Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1urkFk-00BhKC-Ab for pgsql-hackers@arkaria.postgresql.org; Thu, 28 Aug 2025 21:33:54 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1urkFj-004msN-EK for pgsql-hackers@arkaria.postgresql.org; Thu, 28 Aug 2025 21:33:52 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1urkFi-004msE-85 for pgsql-hackers@lists.postgresql.org; Thu, 28 Aug 2025 21:33:51 +0000 Received: from fout-b3-smtp.messagingengine.com ([202.12.124.146]) by makus.postgresql.org with smtp (Exim 4.96) (envelope-from ) id 1urkFf-002Fe1-2H for pgsql-hackers@postgresql.org; Thu, 28 Aug 2025 21:33:49 +0000 Received: from phl-compute-12.internal (phl-compute-12.internal [10.202.2.52]) by mailfout.stl.internal (Postfix) with ESMTP id 9569C1D000C3; Thu, 28 Aug 2025 17:33:46 -0400 (EDT) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-12.internal (MEProxy); Thu, 28 Aug 2025 17:33:46 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kurilemu.de; h= cc:cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to; s=fm2; t=1756416826; x= 1756503226; bh=/y+o/UD7eHKsndZ+Kc5oz2AAWk/NcUKCMppNHflF2hA=; b=A UCpLQm/HsqyNhKelriIFPC6kgBUeWkggBBjl5bz6sucDXSCcRf0JqvvLLPE2pAUa GEpCUVOeKsPVzoAUJvDzbBOTH7fX47VDCAhVF2z9l+PPDJCqhBJ9BO1KBTKOKiaz ilau3b8lZ/ZPpokxJZe0FGD1U47pgDk4qJfKnmIzIT6GPYka9K536hICWQa1XNhg FOHfQIypKlfE4g0V/Ym+BCSFUWKrUN3HO8jlkaJKYNNkYRenXorWscoK6A+u1tOu jFl0uHwWf4F6a7bYkyZxwLAaZZeErYmho1v90zFl7bkXQs+4Gsz/x4HFiRQVCCGI kg1uNDnNKm5h/cASMhSNA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1756416826; x=1756503226; bh=/ y+o/UD7eHKsndZ+Kc5oz2AAWk/NcUKCMppNHflF2hA=; b=jKT5CKYM72LpVnLIT KUGVo2KU2AErxIGbDd1Qm0aimL7ULeiau4FISERroF9TnaXhcWNxKEUF9P5+U6rp aGfhXStNz8Xv4VLFwh4BPpvjA5VpdR1wvEayfTJjj7PW2EFCSx2lTkFNqxyd2dfE /WH5jd9yV4ZTRZKlePSmfomeAiHxBhzXjnop88CXQGpQBvDRvdTNlvqWoAtPjzoJ WQ+uOaNuT+xcpgFSqFAHiwoF1gK2ZsVeuI1ithJKMuMb9BghwjfEwDlUKALeiJc1 eN5L7L/4krwZekXjZUncRkE/CxJBS2fctvv3Ny1RWNxNX3xDMCg/ZJGtSlA1gatj FBejw== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdefgddukedvtdelucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfurfetoffkrfgpnffqhgenuceu rghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujf gurhepfffhvfevuffkgggtugfgjgesmhekreertddtjeenucfhrhhomheplmhlvhgrrhho ucfjvghrrhgvrhgruceorghlvhhhvghrrhgvsehkuhhrihhlvghmuhdruggvqeenucggtf frrghtthgvrhhnpefgheffjeetteehvefhheeuleeghfekgeelvdegieegjefhueetkefg veevjeehheenucffohhmrghinhepvghnthgvrhhprhhishgvuggsrdgtohhmpdhpohhsth hgrhgvshhqlhdrohhrghenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgr ihhlfhhrohhmpegrlhhvhhgvrhhrvgeskhhurhhilhgvmhhurdguvgdpnhgspghrtghpth htohepgedpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtohepjhhirghnrdhunhhivhgv rhhsrghlihhthiesghhmrghilhdrtghomhdprhgtphhtthhopehrvghshhhkvghkihhrih hllhesghhmrghilhdrtghomhdprhgtphhtthhopehpghhsqhhlqdhhrggtkhgvrhhssehp ohhsthhgrhgvshhqlhdrohhrghdprhgtphhtthhopehtghhlsehsshhsrdhpghhhrdhprg druhhs X-ME-Proxy: Feedback-ID: ie3de48e3:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu, 28 Aug 2025 17:33:45 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kurilemu.de; s=schmee; t=1756416822; bh=ax4pZ8ONdNcGR7mPNEvKK53Gc73q2ovOWE0L8IjEKeM=; h=Date:From:To:Cc:Subject:In-Reply-To:From; b=y/d7GkVVYoFQuMglu2qX3yzylYlHqtAG/VJ9EF+3ty57TA9TR5fTEVWkutXYSlx0G v5FOgBlemHpodU/zoE4QhyLIT5tGlbaiKEG8BcT2ufPE376eoqiy2oj3sbeYptPNeF EqZVvgY+NYAzpHG08W25wle1yGMYHyuzfapb9p0AAzXicVeiwuWHAUuJJgPX4xJxn0 7FCQctAGbHDuAFV5fcbX+GUavTxOZXD/Ifyb/xOn8IJivnss05lsy7pswqKWU1URAk 0BBB6KdpByv1B2k+RSEND0VB556yfgl65uBalRTEnA4XYRJu2c8H+g6QrDpMuVQi0X VbBXbJF2hPd8A== Received: by schmee.kurilemu.internal (Postfix, from userid 1000) id 8A6976E; Thu, 28 Aug 2025 23:33:42 +0200 (CEST) Date: Thu, 28 Aug 2025 23:33:42 +0200 From: =?utf-8?Q?=C3=81lvaro?= Herrera To: jian he Cc: PostgreSQL-development , Kirill Reshke , Tom Lane Subject: Re: misleading error message in ProcessUtilitySlow T_CreateStatsStmt Message-ID: <202508282133.uwntzsze7cdl@alvherre.pgsql> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="pbl4mx6qlbtkw6pf" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --pbl4mx6qlbtkw6pf Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit On 2025-Aug-21, jian he wrote: > case T_CreateStatsStmt: > { > Oid relid; > CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; > RangeVar *rel = (RangeVar *) linitial(stmt->relations); > > if (!IsA(rel, RangeVar)) > ereport(ERROR, > (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > errmsg("only a single relation is > allowed in CREATE STATISTICS"))); > relid = RangeVarGetRelid(rel, > ShareUpdateExclusiveLock, false); > /* Run parse analysis ... */ > stmt = transformStatsStmt(relid, stmt, queryString); > address = CreateStatistics(stmt); > } Yeah, I think there's room to argue that this ereport() is just wrongly copy-and-pasted from other nearby errors, and that it should have completely different wording. BTW another way to cause this is CREATE STATISTICS alt_stat2 ON a, b FROM xmltable('foo' passing 'bar' columns a text); I propose to use this report: ERROR: cannot create statistics on specified relation DETAIL: CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables. (Not sold on saying just "tables" vs. "regular tables" or "plain tables"; thoughts?) While looking at this whole thing, I noticed that this code is somewhat bogus in a different way: we're resolving the relation name twice, both here in ProcessUtilitySlow() (to pass to transformStatsStmt), and later again inside CreateStatistics(). It's really strange that we decided not to pass the relation Oids as a list argument to CreateStatistics. I suggest to change that as the first attached patch. Now, such a change would be appropriate only for branch master; it seems too invasive for stable ones. For them I propose we only change the error message, as in the other attachment. We should add a couple of test cases for this particular error message also. It's bad that these changes don't break any tests. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Once again, thank you and all of the developers for your hard work on PostgreSQL. This is by far the most pleasant management experience of any database I've worked on." (Dan Harris) http://archives.postgresql.org/pgsql-performance/2006-04/msg00247.php --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-rewrite-ProcessUtilitySlow-code-for-CreateStatsStmt.patch" From bebffcaa22a047c61b0fa588db56db60e59dc9ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Thu, 28 Aug 2025 19:49:14 +0200 Subject: [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" From 0ad9908e2ad2762445807136784b18777fac4c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Thu, 28 Aug 2025 19:53:44 +0200 Subject: [PATCH] CREATE STATISTICS: Fix error message --- src/backend/tcop/utility.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 25fe3d58016..7f5076d2c1a 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1883,7 +1883,8 @@ ProcessUtilitySlow(ParseState *pstate, if (!IsA(rel, RangeVar)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."))); /* * CREATE STATISTICS will influence future execution plans -- 2.39.5 --pbl4mx6qlbtkw6pf--