agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt 267+ messages / 2 participants [nested] [flat]
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 267+ messages in thread
* [PATCH v2 1/1] Remove unnecessary volatile qualifiers. @ 2026-07-06 15:24 Nathan Bossart <[email protected]> 0 siblings, 0 replies; 267+ messages in thread From: Nathan Bossart @ 2026-07-06 15:24 UTC (permalink / raw) This commit cleans up volatile qualifiers that fit the below criteria: * Accesses to shared memory protected by a spinlock or LWLock. Before commit 0709b7ee72, callers had to use volatile when accessing spinlock-protected shared memory. Since spinlock acquire/release became compiler barriers, and because LWLocks provide the same guarantee, that is no longer necessary. These either predate that change or were cargo-culted from code that did. * Pointers used only to find the address of a member. The volatile qualifier only affects accesses made by dereferencing the pointer, so it is unnecessary there. * Accesses to struct members that are marked volatile in the struct definition. There's no need to mark these pointers volatile, either. * Leftovers from removed PG_TRY blocks. These were marked volatile to protect a value that is modified inside a PG_TRY block and used afterward, but the PG_TRY has since been removed. --- src/backend/access/transam/clog.c | 7 +++--- src/backend/catalog/index.c | 2 +- src/backend/commands/async.c | 4 ++-- src/backend/replication/syncrep.c | 19 +++++++--------- src/backend/storage/ipc/procsignal.c | 8 +++---- src/backend/storage/ipc/shm_toc.c | 31 ++++++++++++--------------- src/backend/storage/lmgr/lock.c | 2 +- src/backend/storage/lmgr/proc.c | 3 +-- src/test/modules/test_shm_mq/setup.c | 4 ++-- src/test/modules/test_shm_mq/worker.c | 2 +- 10 files changed, 37 insertions(+), 45 deletions(-) diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index 75012d4b8f0..6f7f6b86eb6 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -450,7 +450,6 @@ static bool TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status, XLogRecPtr lsn, int64 pageno) { - volatile PROC_HDR *procglobal = ProcGlobal; PGPROC *proc = MyProc; uint32 nextidx; uint32 wakeidx; @@ -493,7 +492,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status, * different from ours. If another group starts to update a page in the * same bank as ours, they wait until we release the lock. */ - nextidx = pg_atomic_read_u32(&procglobal->clogGroupFirst); + nextidx = pg_atomic_read_u32(&ProcGlobal->clogGroupFirst); while (true) { @@ -524,7 +523,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status, pg_atomic_write_u32(&proc->clogGroupNext, nextidx); - if (pg_atomic_compare_exchange_u32(&procglobal->clogGroupFirst, + if (pg_atomic_compare_exchange_u32(&ProcGlobal->clogGroupFirst, &nextidx, (uint32) MyProcNumber)) break; @@ -576,7 +575,7 @@ TransactionGroupUpdateXidStatus(TransactionId xid, XidStatus status, * At this point, any processes trying to do this would create a separate * group. */ - nextidx = pg_atomic_exchange_u32(&procglobal->clogGroupFirst, + nextidx = pg_atomic_exchange_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER); /* Remember head of list so we can perform wakeups after dropping lock. */ diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 9407c357f27..81bba4beac7 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -3637,7 +3637,7 @@ reindex_index(const ReindexStmt *stmt, Oid indexId, int save_sec_context; int save_nestlevel; IndexInfo *indexInfo; - volatile bool skipped_constraint = false; + bool skipped_constraint = false; PGRUsage ru0; bool progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0); bool set_tablespace = false; diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index eee8bc29f38..2799f989b96 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -605,7 +605,7 @@ static void CleanupListenersOnExit(void); static bool IsListeningOn(const char *channel); static void asyncQueueUnregister(void); static bool asyncQueueIsFull(void); -static bool asyncQueueAdvance(volatile QueuePosition *position, int entryLength); +static bool asyncQueueAdvance(QueuePosition *position, int entryLength); static void asyncQueueNotificationToEntry(Notification *n, AsyncQueueEntry *qe); static ListCell *asyncQueueAddEntries(ListCell *nextNotify); static double asyncQueueUsage(void); @@ -1968,7 +1968,7 @@ asyncQueueIsFull(void) * returns true, else false. */ static bool -asyncQueueAdvance(volatile QueuePosition *position, int entryLength) +asyncQueueAdvance(QueuePosition *position, int entryLength) { int64 pageno = QUEUE_POS_PAGE(*position); int offset = QUEUE_POS_OFFSET(*position); diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index e0e30579c59..d870f09e0a0 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -483,7 +483,6 @@ SyncRepInitConfig(void) void SyncRepReleaseWaiters(void) { - volatile WalSndCtlData *walsndctl = WalSndCtl; XLogRecPtr writePtr; XLogRecPtr flushPtr; XLogRecPtr applyPtr; @@ -558,19 +557,19 @@ SyncRepReleaseWaiters(void) * Set the lsn first so that when we wake backends they will release up to * this location. */ - if (walsndctl->lsn[SYNC_REP_WAIT_WRITE] < writePtr) + if (WalSndCtl->lsn[SYNC_REP_WAIT_WRITE] < writePtr) { - walsndctl->lsn[SYNC_REP_WAIT_WRITE] = writePtr; + WalSndCtl->lsn[SYNC_REP_WAIT_WRITE] = writePtr; numwrite = SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE); } - if (walsndctl->lsn[SYNC_REP_WAIT_FLUSH] < flushPtr) + if (WalSndCtl->lsn[SYNC_REP_WAIT_FLUSH] < flushPtr) { - walsndctl->lsn[SYNC_REP_WAIT_FLUSH] = flushPtr; + WalSndCtl->lsn[SYNC_REP_WAIT_FLUSH] = flushPtr; numflush = SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH); } - if (walsndctl->lsn[SYNC_REP_WAIT_APPLY] < applyPtr) + if (WalSndCtl->lsn[SYNC_REP_WAIT_APPLY] < applyPtr) { - walsndctl->lsn[SYNC_REP_WAIT_APPLY] = applyPtr; + WalSndCtl->lsn[SYNC_REP_WAIT_APPLY] = applyPtr; numapply = SyncRepWakeQueue(false, SYNC_REP_WAIT_APPLY); } @@ -777,8 +776,7 @@ SyncRepGetCandidateStandbys(SyncRepStandbyData **standbys) n = 0; for (i = 0; i < max_wal_senders; i++) { - volatile WalSnd *walsnd; /* Use volatile pointer to prevent code - * rearrangement */ + WalSnd *walsnd; SyncRepStandbyData *stby; WalSndState state; /* not included in SyncRepStandbyData */ @@ -915,7 +913,6 @@ SyncRepGetStandbyPriority(void) static int SyncRepWakeQueue(bool all, int mode) { - volatile WalSndCtlData *walsndctl = WalSndCtl; int numprocs = 0; dlist_mutable_iter iter; @@ -930,7 +927,7 @@ SyncRepWakeQueue(bool all, int mode) /* * Assume the queue is ordered by LSN */ - if (!all && walsndctl->lsn[mode] < proc->waitLSN) + if (!all && WalSndCtl->lsn[mode] < proc->waitLSN) return numprocs; /* diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index 1397f65f67b..21a77f98c1d 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -295,7 +295,7 @@ CleanupProcSignalState(int status, Datum arg) int SendProcSignal(pid_t pid, ProcSignalReason reason, ProcNumber procNumber) { - volatile ProcSignalSlot *slot; + ProcSignalSlot *slot; if (procNumber != INVALID_PROC_NUMBER) { @@ -380,7 +380,7 @@ EmitProcSignalBarrier(ProcSignalBarrierType type) */ for (int i = 0; i < NumProcSignalSlots; i++) { - volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; + ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; pg_atomic_fetch_or_u32(&slot->pss_barrierCheckMask, flagbit); } @@ -406,7 +406,7 @@ EmitProcSignalBarrier(ProcSignalBarrierType type) */ for (int i = NumProcSignalSlots - 1; i >= 0; i--) { - volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; + ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; pid_t pid = pg_atomic_read_u32(&slot->pss_pid); if (pid != 0) @@ -670,7 +670,7 @@ ResetProcSignalBarrierBits(uint32 flags) static bool CheckProcSignal(ProcSignalReason reason) { - volatile ProcSignalSlot *slot = MyProcSignalSlot; + ProcSignalSlot *slot = MyProcSignalSlot; if (slot != NULL) { diff --git a/src/backend/storage/ipc/shm_toc.c b/src/backend/storage/ipc/shm_toc.c index 2f9fbb0a519..2217d48c3d9 100644 --- a/src/backend/storage/ipc/shm_toc.c +++ b/src/backend/storage/ipc/shm_toc.c @@ -87,7 +87,6 @@ shm_toc_attach(uint64 magic, void *address) void * shm_toc_allocate(shm_toc *toc, Size nbytes) { - volatile shm_toc *vtoc = toc; Size total_bytes; Size allocated_bytes; Size nentry; @@ -103,9 +102,9 @@ shm_toc_allocate(shm_toc *toc, Size nbytes) SpinLockAcquire(&toc->toc_mutex); - total_bytes = vtoc->toc_total_bytes; - allocated_bytes = vtoc->toc_allocated_bytes; - nentry = vtoc->toc_nentry; + total_bytes = toc->toc_total_bytes; + allocated_bytes = toc->toc_allocated_bytes; + nentry = toc->toc_nentry; toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry) + allocated_bytes; @@ -117,7 +116,7 @@ shm_toc_allocate(shm_toc *toc, Size nbytes) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of shared memory"))); } - vtoc->toc_allocated_bytes += nbytes; + toc->toc_allocated_bytes += nbytes; SpinLockRelease(&toc->toc_mutex); @@ -130,16 +129,15 @@ shm_toc_allocate(shm_toc *toc, Size nbytes) Size shm_toc_freespace(shm_toc *toc) { - volatile shm_toc *vtoc = toc; Size total_bytes; Size allocated_bytes; Size nentry; Size toc_bytes; SpinLockAcquire(&toc->toc_mutex); - total_bytes = vtoc->toc_total_bytes; - allocated_bytes = vtoc->toc_allocated_bytes; - nentry = vtoc->toc_nentry; + total_bytes = toc->toc_total_bytes; + allocated_bytes = toc->toc_allocated_bytes; + nentry = toc->toc_nentry; SpinLockRelease(&toc->toc_mutex); toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry); @@ -170,7 +168,6 @@ shm_toc_freespace(shm_toc *toc) void shm_toc_insert(shm_toc *toc, uint64 key, void *address) { - volatile shm_toc *vtoc = toc; Size total_bytes; Size allocated_bytes; Size nentry; @@ -183,14 +180,14 @@ shm_toc_insert(shm_toc *toc, uint64 key, void *address) SpinLockAcquire(&toc->toc_mutex); - total_bytes = vtoc->toc_total_bytes; - allocated_bytes = vtoc->toc_allocated_bytes; - nentry = vtoc->toc_nentry; + total_bytes = toc->toc_total_bytes; + allocated_bytes = toc->toc_allocated_bytes; + nentry = toc->toc_nentry; #ifdef USE_ASSERT_CHECKING /* Verify no duplicate keys */ for (Size i = 0; i < nentry; i++) - Assert(vtoc->toc_entry[i].key != key); + Assert(toc->toc_entry[i].key != key); #endif toc_bytes = offsetof(shm_toc, toc_entry) + nentry * sizeof(shm_toc_entry) @@ -208,8 +205,8 @@ shm_toc_insert(shm_toc *toc, uint64 key, void *address) } Assert(offset < total_bytes); - vtoc->toc_entry[nentry].key = key; - vtoc->toc_entry[nentry].offset = offset; + toc->toc_entry[nentry].key = key; + toc->toc_entry[nentry].offset = offset; /* * By placing a write barrier after filling in the entry and before @@ -218,7 +215,7 @@ shm_toc_insert(shm_toc *toc, uint64 key, void *address) */ pg_write_barrier(); - vtoc->toc_nentry++; + toc->toc_nentry++; SpinLockRelease(&toc->toc_mutex); } diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 8d246ed5a4e..5ee80c7632e 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -312,7 +312,7 @@ typedef struct uint32 count[FAST_PATH_STRONG_LOCK_HASH_PARTITIONS]; } FastPathStrongRelationLockData; -static volatile FastPathStrongRelationLockData *FastPathStrongRelationLocks; +static FastPathStrongRelationLockData *FastPathStrongRelationLocks; static void LockManagerShmemRequest(void *arg); static void LockManagerShmemInit(void *arg); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 7d01c981a1f..87dd289f626 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -660,8 +660,7 @@ InitAuxiliaryProcess(void) } /* Mark auxiliary proc as in use by me */ - /* use volatile pointer to prevent code rearrangement */ - ((volatile PGPROC *) auxproc)->pid = MyProcPid; + auxproc->pid = MyProcPid; SpinLockRelease(&ProcGlobal->freeProcsLock); diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c index 4f40a61e3d9..991fe27a7fa 100644 --- a/src/test/modules/test_shm_mq/setup.c +++ b/src/test/modules/test_shm_mq/setup.c @@ -38,7 +38,7 @@ static worker_state *setup_background_workers(int nworkers, dsm_segment *seg); static void cleanup_background_workers(dsm_segment *seg, Datum arg); static void wait_for_workers_to_become_ready(worker_state *wstate, - volatile test_shm_mq_header *hdr); + test_shm_mq_header *hdr); static bool check_worker_status(worker_state *wstate); /* value cached, fetched from shared memory */ @@ -258,7 +258,7 @@ cleanup_background_workers(dsm_segment *seg, Datum arg) static void wait_for_workers_to_become_ready(worker_state *wstate, - volatile test_shm_mq_header *hdr) + test_shm_mq_header *hdr) { bool result = false; diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c index e13c05ae5c7..0ba1cfcac47 100644 --- a/src/test/modules/test_shm_mq/worker.c +++ b/src/test/modules/test_shm_mq/worker.c @@ -52,7 +52,7 @@ test_shm_mq_main(Datum main_arg) shm_toc *toc; shm_mq_handle *inqh; shm_mq_handle *outqh; - volatile test_shm_mq_header *hdr; + test_shm_mq_header *hdr; int myworkernumber; PGPROC *registrant; -- 2.50.1 (Apple Git-155) --z39t9WErpHrlykOW-- ^ permalink raw reply [nested|flat] 267+ messages in thread
end of thread, other threads:[~2026-07-06 15:24 UTC | newest] Thread overview: 267+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2026-07-06 15:24 [PATCH v2 1/1] Remove unnecessary volatile qualifiers. Nathan Bossart <[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