agora inbox for [email protected]  
help / color / mirror / Atom feed
Re: Providing catalog view to pg_hba.conf file - Patch submission
293+ messages / 10 participants
[nested] [flat]

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-03 15:08  Greg Stark <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Greg Stark @ 2015-03-03 15:08 UTC (permalink / raw)
  To: Abhijit Menon-Sen <[email protected]>; +Cc: pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Mon, Jun 30, 2014 at 8:06 AM, Abhijit Menon-Sen <[email protected]> wrote:
> After sleeping on it, I realised that the code would return '{all}' for
> 'all' in pg_hba.conf, but '{"all"}' for '"all"'. So it's not exactly
> ambiguous, but I don't think it's especially useful for callers.

Hm. Nope, it doesn't. It just says {all} regardless of whether "all"
is quoted or not.

This makes sense, the rules for when to quote things in pg_hba and
when to quote things for arrays are separate. And we definitely don't
want to start adding quotes to every token that the parser noted was
quoted because then you'll start seeing things like {"\"all\""} and
{"\"database with space\""} which won't make it any easier to match
things.

I'm not sure adding a separate column is really the solution either.
You can have things like all,databasename (which is the keyword "all"
not a database "all). Or more likely something like sameuser,bob or
even things like replication,all.

I'm thinking leaving well enough alone is probably best. It's not
perfect but if a user does have a database named "all" or
"replication" or a user named "sameuser" or "samerole" then it's not
like pg_hba_settings crashes or anything, it just produces information
that's hard to interpret and the response might just be "don't do
that".

The only other option I was pondering was using a jsonb instead of an
array. That would give us more flexibility and we could have a json
array that contained strings and objects representing keywords. But
most hba files consist *mostly* of singleton keywords,  so the result
might be kind of cumbersome.

-- 
greg


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-03 18:05  Jim Nasby <[email protected]>
  parent: Greg Stark <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Jim Nasby @ 2015-03-03 18:05 UTC (permalink / raw)
  To: Greg Stark <[email protected]>; Abhijit Menon-Sen <[email protected]>; +Cc: pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On 3/3/15 9:08 AM, Greg Stark wrote:
> On Mon, Jun 30, 2014 at 8:06 AM, Abhijit Menon-Sen <[email protected]> wrote:
>> After sleeping on it, I realised that the code would return '{all}' for
>> 'all' in pg_hba.conf, but '{"all"}' for '"all"'. So it's not exactly
>> ambiguous, but I don't think it's especially useful for callers.
>
> Hm. Nope, it doesn't. It just says {all} regardless of whether "all"
> is quoted or not.
>
> This makes sense, the rules for when to quote things in pg_hba and
> when to quote things for arrays are separate. And we definitely don't
> want to start adding quotes to every token that the parser noted was
> quoted because then you'll start seeing things like {"\"all\""} and
> {"\"database with space\""} which won't make it any easier to match
> things.
>
> I'm not sure adding a separate column is really the solution either.
> You can have things like all,databasename (which is the keyword "all"
> not a database "all). Or more likely something like sameuser,bob or
> even things like replication,all.

What about a separate column that's just the text from pg_hba? Or is 
that what you're opposed to?

FWIW, I'd say that having the individual array elements be correct is 
more important than what the result of array_out is. That way you could 
always do array_to_string(..., ', ') and get valid pg_hba output.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-03 18:57  Greg Stark <[email protected]>
  parent: Jim Nasby <[email protected]>
  0 siblings, 2 replies; 293+ messages in thread

From: Greg Stark @ 2015-03-03 18:57 UTC (permalink / raw)
  To: Jim Nasby <[email protected]>; +Cc: Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Tue, Mar 3, 2015 at 6:05 PM, Jim Nasby <[email protected]> wrote:

> What about a separate column that's just the text from pg_hba? Or is that what you're opposed to?

I'm not sure what you mean by that. There's a rawline field we could
put somewhere but it contains the entire line.

> FWIW, I'd say that having the individual array elements be correct is more
> important than what the result of array_out is. That way you could always do
> array_to_string(..., ', ') and get valid pg_hba output.

Well I don't think you can get that without making the view less
useful for every other purpose.

Like, I would want to be able to do WHERE "user" @> array[?] or WHERE
database = array[?] or to join against a list of users or databases
somewhere else.

To do what you suggest would mean the tokens will need to be quoted
based on pg_hba.conf syntax requirements. That would mean I would need
to check each variable or join value against pg_hba.conf's quoting
requirements to compare with it. It seems more practical to have that
knowledge if you're actually going to generate a pg_hba.conf than to
pass around these quoted strings all the time.

On further review I've made a few more changes attached.

I think we should change the column names to "users" and "databases"
to be clear they're lists and also to avoid the "user" SQL reserved
word.

I removed the dependency on strlist_to_array which is in
objectaddress.c which isn't a very sensible dependency -- it does seem
like it would be handy to have a list-based version of construct_array
moved to arrayfuncs.c but for now it's not much more work to handle
these ourselves.

I changed the options to accumulate one big array instead of an array
of bunches of options. Previously you could only end up with a
singleton array with a comma-delimited string or a two element array
with one of those and map=.

I think the error if pg_hba fails to reload needs to be LOG. It would
be too unexpected to the user who isn't necessarily the one who issued
the SIGHUP to spontaneously get a warning.

I also removed the "mask" from local entries and made some of the
NULLS that shouldn't be possible to happen (unknown auth method or
connection method) actually throw errors.

-- 
greg


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [text/x-patch] Catalog_view_to_HBA_settings_patch_V6S2.patch (19.6K, ../../CAM-w4HM_s77AdoXq3Xdu7sYOd-SpRr8E6Em_tuV4hH5=4E14tQ@mail.gmail.com/2-Catalog_view_to_HBA_settings_patch_V6S2.patch)
  download | inline diff:
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 7393,7398 ****
--- 7393,7403 ----
        <entry>views</entry>
       </row>
  
+      <row>
+       <entry><link linkend="view-pg-hba-settings"><structname>pg_hba_settings</structname></link></entry>
+       <entry>client authentication settings</entry>
+      </row>
+ 
      </tbody>
     </tgroup>
    </table>
***************
*** 9696,9699 **** SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
--- 9701,9786 ----
  
   </sect1>
  
+  <sect1 id="view-pg-hba-settings">
+   <title><structname>pg_hba_settings</structname></title>
+ 
+   <indexterm zone="view-pg-hba-settings">
+    <primary>pg_hba_settings</primary>
+   </indexterm>
+ 
+   <para>
+    The read-only <structname>pg_hba_settings</structname> view provides
+    access to the client authentication configuration from pg_hba.conf.
+    Access to this view is limited to superusers. 
+   </para>
+ 
+   <table>
+    <title><structname>pg_hba_settings</> Columns</title>
+ 
+    <tgroup cols="3">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Type</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><structfield>type</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Type of connection</entry>
+      </row>
+      <row>
+       <entry><structfield>databases</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>List of database names</entry>
+      </row>
+      <row>
+       <entry><structfield>users</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>List of user names</entry>
+      </row>
+      <row>
+       <entry><structfield>address</structfield></entry>
+       <entry><type>inet</type></entry>
+       <entry>Client machine address</entry>
+      </row>
+      <row>
+       <entry><structfield>mask</structfield></entry>
+       <entry><type>inet</type></entry>
+       <entry>IP Mask</entry>
+      </row>
+      <row>
+       <entry><structfield>compare_method</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>IP address comparison method</entry>
+      </row>
+      <row>
+       <entry><structfield>hostname</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Client host name</entry>
+      </row>
+      <row>
+       <entry><structfield>method</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Authentication method</entry>
+      </row>
+      <row>
+       <entry><structfield>options</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>Configuration options set for authentication method</entry>
+      </row>
+      <row>
+       <entry><structfield>line_number</structfield></entry>
+       <entry><type>integer</type></entry>
+       <entry>
+        Line number within client authentication configuration file 
+        the current value was set at
+       </entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </sect1>
  </chapter>
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
***************
*** 680,685 **** local   all             @admins,+support                        md5
--- 680,690 ----
  local   db1,db2,@demodbs  all                                   md5
  </programlisting>
     </example>
+ 
+    <para>
+     The contents of this file are reflected in the pg_hba_settings view.
+     See <xref linkend="view-pg-hba-settings"> for details.
+    </para>
   </sect1>
  
   <sect1 id="auth-username-maps">
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 414,419 **** CREATE RULE pg_settings_n AS
--- 414,424 ----
  
  GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;
  
+ CREATE VIEW pg_hba_settings AS
+     SELECT * FROM pg_hba_settings() AS A;
+ 
+ REVOKE ALL on pg_hba_settings FROM public;
+ 
  CREATE VIEW pg_timezone_abbrevs AS
      SELECT * FROM pg_timezone_abbrevs();
  
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 25,38 ****
--- 25,43 ----
  #include <arpa/inet.h>
  #include <unistd.h>
  
+ #include "access/htup_details.h"
  #include "catalog/pg_collation.h"
+ #include "catalog/pg_type.h"
+ #include "funcapi.h"
  #include "libpq/ip.h"
  #include "libpq/libpq.h"
+ #include "miscadmin.h"
  #include "postmaster/postmaster.h"
  #include "regex/regex.h"
  #include "replication/walsender.h"
  #include "storage/fd.h"
  #include "utils/acl.h"
+ #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
***************
*** 2220,2222 **** hba_getauthmethod(hbaPort *port)
--- 2225,2654 ----
  {
  	check_hba(port);
  }
+ 
+ 
+ /* LDAP supports 10 currently, keep this well above the most any method needs */
+ #define MAX_OPTIONS 16
+ 
+ /*
+  * Fill in suitable values to build a tuple representing the
+  * HbaLine provided
+  */
+ static void
+ hba_getvalues_for_line(HbaLine *hba, Datum *values, bool *nulls)
+ {
+ 	ListCell   *dbcell;
+ 	char		buffer[NI_MAXHOST];
+ 	StringInfoData str;
+ 	int			index = 0;
+ 	int			noptions;
+ 	Datum		options[MAX_OPTIONS];
+ 
+ 	/* connection type */
+ 	switch (hba->conntype)
+ 	{
+ 		case ctLocal:
+ 			values[index] = CStringGetTextDatum("local");
+ 			break;
+ 		case ctHost:
+ 			values[index] = CStringGetTextDatum("host");
+ 			break;
+ 		case ctHostSSL:
+ 			values[index] = CStringGetTextDatum("hostssl");
+ 			break;
+ 		case ctHostNoSSL:
+ 			values[index] = CStringGetTextDatum("hostnossl");
+ 			break;
+ 		default:
+ 			elog(ERROR, "Unexpected Connection Type in parsed HBA entry");
+ 			break;
+ 	}
+ 
+ 	/* databases */
+ 	index++;
+ 	if (list_length(hba->databases) != 0)
+ 	{
+ 		int			j = 0;
+ 		HbaToken   *tok;
+ 		Datum	   *names = palloc(sizeof(Datum) * list_length(hba->databases));
+ 
+ 		foreach(dbcell, hba->databases)
+ 		{
+ 			tok = lfirst(dbcell);
+ 			names[j++] = CStringGetTextDatum(tok->string);
+ 		}
+ 
+ 		values[index] = PointerGetDatum(construct_array(names, list_length(hba->databases),
+ 														TEXTOID, -1, false, 'i'));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* users */
+ 	index++;
+ 	if (list_length(hba->roles) != 0)
+ 	{
+ 		int			j = 0;
+ 		HbaToken   *tok;
+ 		Datum	   *roles = palloc(sizeof(Datum) * list_length(hba->roles));
+ 
+ 		foreach(dbcell, hba->roles)
+ 		{
+ 			tok = lfirst(dbcell);
+ 			roles[j++] = CStringGetTextDatum(tok->string);
+ 		}
+ 
+ 		values[index] = PointerGetDatum(construct_array(roles, list_length(hba->roles),
+ 														TEXTOID, -1, false, 'i'));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* address */
+ 	index++;
+ 	if (pg_getnameinfo_all(&hba->addr, sizeof(struct sockaddr_storage),
+ 						   buffer, sizeof(buffer),
+ 						   NULL, 0,
+ 						   NI_NUMERICHOST) == 0)
+ 	{
+ 		clean_ipv6_addr(hba->addr.ss_family, buffer);
+ 		values[index] = DirectFunctionCall1(inet_in, CStringGetDatum(buffer));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* mask */
+ 	index++;
+ 	if (pg_getnameinfo_all(&hba->mask, sizeof(struct sockaddr_storage),
+ 						   buffer, sizeof(buffer),
+ 						   NULL, 0,
+ 						   NI_NUMERICHOST) == 0)
+ 	{
+ 		clean_ipv6_addr(hba->addr.ss_family, buffer);
+ 		values[index] = DirectFunctionCall1(inet_in, CStringGetDatum(buffer));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* compare method */
+ 	index++;
+ 	if (hba->conntype == ctLocal)
+ 		nulls[index] = true;
+ 	else if (hba->ip_cmp_method == ipCmpMask)
+ 		values[index] = CStringGetTextDatum("mask");
+ 	else if (hba->ip_cmp_method == ipCmpSameHost)
+ 		values[index] = CStringGetTextDatum("samehost");
+ 	else if (hba->ip_cmp_method == ipCmpSameNet)
+ 		values[index] = CStringGetTextDatum("samenet");
+ 	else if (hba->ip_cmp_method == ipCmpAll)
+ 		values[index] = CStringGetTextDatum("all");
+ 	else
+ 		elog(ERROR, "Unexpected Compare Method in parsed HBA entry");
+ 
+ 	/* hostname */
+ 	index++;
+ 	if (hba->hostname)
+ 		values[index] = CStringGetTextDatum(hba->hostname);
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* method */
+ 	index++;
+ 	switch (hba->auth_method)
+ 	{
+ 		case uaReject:
+ 			values[index] = CStringGetTextDatum("reject");
+ 			break;
+ 		case uaImplicitReject:
+ 			values[index] = CStringGetTextDatum("implicitreject");
+ 			break;
+ 		case uaTrust:
+ 			values[index] = CStringGetTextDatum("trust");
+ 			break;
+ 		case uaIdent:
+ 			values[index] = CStringGetTextDatum("ident");
+ 			break;
+ 		case uaPassword:
+ 			values[index] = CStringGetTextDatum("password");
+ 			break;
+ 		case uaMD5:
+ 			values[index] = CStringGetTextDatum("md5");
+ 			break;
+ 		case uaGSS:
+ 			values[index] = CStringGetTextDatum("gss");
+ 			break;
+ 		case uaSSPI:
+ 			values[index] = CStringGetTextDatum("sspi");
+ 			break;
+ 		case uaPAM:
+ 			values[index] = CStringGetTextDatum("pam");
+ 			break;
+ 		case uaLDAP:
+ 			values[index] = CStringGetTextDatum("ldap");
+ 			break;
+ 		case uaCert:
+ 			values[index] = CStringGetTextDatum("cert");
+ 			break;
+ 		case uaRADIUS:
+ 			values[index] = CStringGetTextDatum("radius");
+ 			break;
+ 		case uaPeer:
+ 			values[index] = CStringGetTextDatum("peer");
+ 			break;
+ 		default:
+ 			elog(ERROR, "Unexpected Auth Method in parsed HBA entry");
+ 			break;
+ 	}
+ 
+ 	/* options */
+ 	index++;
+ 	noptions = 0;
+ 
+ 	if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
+ 	{
+ 		if (hba->include_realm)
+ 			options[noptions++] = CStringGetTextDatum("include_realm=true");
+ 
+ 		if (hba->krb_realm)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "krb_realm=");
+ 			appendStringInfoString(&str, hba->krb_realm);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	if (hba->usermap)
+ 	{
+ 		initStringInfo(&str);
+ 		appendStringInfoString(&str, "map=");
+ 		appendStringInfoString(&str, hba->usermap);
+ 		options[noptions++] = CStringGetTextDatum(str.data);
+ 	}
+ 
+ 	if (hba->auth_method == uaLDAP)
+ 	{
+ 		if (hba->ldapserver)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapserver=");
+ 			appendStringInfoString(&str, hba->ldapserver);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapport)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->ldapport);
+ 			appendStringInfoString(&str, "ldapport=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldaptls)
+ 			options[noptions++] = CStringGetTextDatum("ldaptls=true");
+ 
+ 		if (hba->ldapprefix)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapprefix=");
+ 			appendStringInfoString(&str, hba->ldapprefix);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapsuffix)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapsuffix=");
+ 			appendStringInfoString(&str, hba->ldapsuffix);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbasedn)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbasedn=");
+ 			appendStringInfoString(&str, hba->ldapbasedn);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbinddn)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbinddn=");
+ 			appendStringInfoString(&str, hba->ldapbinddn);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbindpasswd)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbindpasswd=");
+ 			appendStringInfoString(&str, hba->ldapbindpasswd);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapsearchattribute)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapsearchattribute=");
+ 			appendStringInfoString(&str, hba->ldapsearchattribute);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapscope)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->ldapscope);
+ 			appendStringInfoString(&str, "ldapscope=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	if (hba->auth_method == uaRADIUS)
+ 	{
+ 		if (hba->radiusserver)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiusserver=");
+ 			appendStringInfoString(&str, hba->radiusserver);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiussecret)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiussecret=");
+ 			appendStringInfoString(&str, hba->radiussecret);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiusidentifier)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiusidentifier=");
+ 			appendStringInfoString(&str, hba->radiusidentifier);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiusport)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->radiusport);
+ 			appendStringInfoString(&str, "radiusport=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	Assert(noptions <= MAX_OPTIONS);
+ 	if (noptions)
+ 		values[index] = PointerGetDatum(
+ 				construct_array(options, noptions, TEXTOID, -1, false, 'i'));
+ 	else
+ 		/* Probably should be {} but that makes for a messy looking view */
+ 		nulls[index] = true;
+ 
+ 	/* line_number */
+ 	index++;
+ 	values[index] = Int32GetDatum(hba->linenumber);
+ }
+ 
+ 
+ #define NUM_PG_HBA_SETTINGS_ATTS   10
+ 
+ /*
+  * SQL-accessible SRF to return all the settings from the pg_hba.conf
+  * file. See the pga_hba_settings view in the "System Catalogs" section of the
+  * manual.
+  */
+ 
+ Datum
+ hba_settings(PG_FUNCTION_ARGS)
+ {
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	tupdesc;
+ 	ListCell   *line;
+ 	MemoryContext old_cxt;
+ 
+ 	/*
+ 	 * We must use the Materialize mode to be safe against HBA file reloads
+ 	 * while the cursor is open. It's also more efficient than having to look
+ 	 * up our current position in the parsed list every time.
+ 	 */
+ 
+ 	ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	/*
+ 	 * Create the tupledesc and tuplestore in the per_query context as
+ 	 * required for SFRM_Materialize.
+ 	 */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	tupdesc = CreateTemplateTupleDesc(NUM_PG_HBA_SETTINGS_ATTS, false);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "type",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "databases",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "users",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "address",
+ 					   INETOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "mask",
+ 					   INETOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "compare_method",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "hostname",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "method",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "options",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "line_number",
+ 					   INT4OID, -1, 0);
+ 	BlessTupleDesc(tupdesc);
+ 
+ 	tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	/*
+ 	 * Loop through the list and deparse each entry as it comes, storing it in
+ 	 * the tuplestore. Any temporary memory allocations here live only for the
+ 	 * function call lifetime.
+ 	 */
+ 	foreach(line, parsed_hba_lines)
+ 	{
+ 		HbaLine    *hba = (HbaLine *) lfirst(line);
+ 		Datum		values[NUM_PG_HBA_SETTINGS_ATTS];
+ 		bool		nulls[NUM_PG_HBA_SETTINGS_ATTS];
+ 		HeapTuple	tuple;
+ 
+ 		MemSet(values, 0, sizeof(values));
+ 		MemSet(nulls, 0, sizeof(nulls));
+ 
+ 		/* Get the next parsed hba line values */
+ 		hba_getvalues_for_line(hba, values, nulls);
+ 
+ 		/* build a tuple */
+ 		tuple = heap_form_tuple(tupdesc, values, nulls);
+ 		tuplestore_puttuple(tuple_store, tuple);
+ 	}
+ 
+ 	rsi->setDesc = tupdesc;
+ 	rsi->setResult = tuple_store;
+ 
+ 	PG_RETURN_NULL();
+ }
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
***************
*** 3996,4001 **** PostgresMain(int argc, char *argv[],
--- 3996,4009 ----
  		{
  			got_SIGHUP = false;
  			ProcessConfigFile(PGC_SIGHUP);
+ 			
+ 			/* 
+ 			 * Reload authentication config files too to refresh 
+ 			 * pg_hba_settings view data.
+ 			 */
+ 			if (!load_hba())
+ 				ereport(LOG,
+ 					(errmsg("pg_hba.conf not reloaded, pg_hba_settings may show stale information")));
  		}
  
  		/*
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 3023,3028 **** DATA(insert OID = 2078 (  set_config		PGNSP PGUID 12 1 0 0 0 f f f f f f v 3 0 2
--- 3023,3030 ----
  DESCR("SET X as a function");
  DATA(insert OID = 2084 (  pg_show_all_settings	PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,25,25,25,25,25,25,25,25,25,25,1009,25,25,25,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{name,setting,unit,category,short_desc,extra_desc,context,vartype,source,min_val,max_val,enumvals,boot_val,reset_val,sourcefile,sourceline}" _null_ show_all_settings _null_ _null_ _null_ ));
  DESCR("SHOW ALL as a function");
+ DATA(insert OID = 3582 (  pg_hba_settings  PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,1009,1009,869,869,25,25,25,1009,23}" "{o,o,o,o,o,o,o,o,o,o}" "{type,databases,users,address,mask,compare_method,hostname,method,options,line_number}" _null_ hba_settings _null_ _null_ _null_ ));
+ DESCR("view client authentication settings");
  DATA(insert OID = 1371 (  pg_lock_status   PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}" _null_ pg_lock_status _null_ _null_ _null_ ));
  DESCR("view system lock information");
  DATA(insert OID = 1065 (  pg_prepared_xact PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{28,25,1184,26,26}" "{o,o,o,o,o}" "{transaction,gid,prepared,ownerid,dbid}" _null_ pg_prepared_xact _null_ _null_ _null_ ));
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 1090,1095 **** extern Datum quote_nullable(PG_FUNCTION_ARGS);
--- 1090,1096 ----
  extern Datum show_config_by_name(PG_FUNCTION_ARGS);
  extern Datum set_config_by_name(PG_FUNCTION_ARGS);
  extern Datum show_all_settings(PG_FUNCTION_ARGS);
+ extern Datum hba_settings(PG_FUNCTION_ARGS);
  
  /* lockfuncs.c */
  extern Datum pg_lock_status(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
***************
*** 1315,1320 **** pg_group| SELECT pg_authid.rolname AS groname,
--- 1315,1331 ----
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
    WHERE (NOT pg_authid.rolcanlogin);
+ pg_hba_settings| SELECT a.type,
+     a.databases,
+     a.users,
+     a.address,
+     a.mask,
+     a.compare_method,
+     a.hostname,
+     a.method,
+     a.options,
+     a.line_number
+    FROM pg_hba_settings() a(type, databases, users, address, mask, compare_method, hostname, method, options, line_number);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 00:17  Jim Nasby <[email protected]>
  parent: Greg Stark <[email protected]>
  1 sibling, 2 replies; 293+ messages in thread

From: Jim Nasby @ 2015-03-04 00:17 UTC (permalink / raw)
  To: Greg Stark <[email protected]>; +Cc: Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On 3/3/15 12:57 PM, Greg Stark wrote:
> On Tue, Mar 3, 2015 at 6:05 PM, Jim Nasby <[email protected]> wrote:
>
>> What about a separate column that's just the text from pg_hba? Or is that what you're opposed to?
>
> I'm not sure what you mean by that. There's a rawline field we could
> put somewhere but it contains the entire line.

I mean have a field for each of user/databases that gives you valid 
pg_hba.conf output. That would allow for cut & paste. But perhaps that's 
just not a use case.

>> FWIW, I'd say that having the individual array elements be correct is more
>> important than what the result of array_out is. That way you could always do
>> array_to_string(..., ', ') and get valid pg_hba output.
>
> Well I don't think you can get that without making the view less
> useful for every other purpose.
>
> Like, I would want to be able to do WHERE "user" @> array[?] or WHERE
> database = array[?] or to join against a list of users or databases
> somewhere else.

I think we're screwed in that regard anyway, because of the special 
constructs. You'd need different logic to handle things like +role and 
sameuser. We might even end up painted in a corner where we can't change 
it in the future because it'll break everyone's scripts.

> To do what you suggest would mean the tokens will need to be quoted
> based on pg_hba.conf syntax requirements. That would mean I would need
> to check each variable or join value against pg_hba.conf's quoting
> requirements to compare with it. It seems more practical to have that
> knowledge if you're actually going to generate a pg_hba.conf than to
> pass around these quoted strings all the time.

What about this:

- database_specials enum[] contains all occurrences of all, sameuser, 
samerole and replication (or maybe it doesn't need to be an array)
- in_roles name[] is a list of all cases of +role_name, with the + 
stripped off (I think the @ case is already handled before the SRF is 
called??)
- role_specials enum[] handles all (enum[] for any future expansion)

Alternatively in_roles could just be combined with role_specials as long 
as we preserve the +.

Any tokens that match the special conditions do not show up in 
databases/users, and those fields become name[]. AFAIK that means the 
quoting should be identical (at least looking at check_db() and 
check_role()).

I can make these changes if you want.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 00:34  Haribabu Kommi <[email protected]>
  parent: Greg Stark <[email protected]>
  1 sibling, 1 reply; 293+ messages in thread

From: Haribabu Kommi @ 2015-03-04 00:34 UTC (permalink / raw)
  To: Greg Stark <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Wed, Mar 4, 2015 at 5:57 AM, Greg Stark <[email protected]> wrote:
> On further review I've made a few more changes attached.
>
> I think we should change the column names to "users" and "databases"
> to be clear they're lists and also to avoid the "user" SQL reserved
> word.
>
> I removed the dependency on strlist_to_array which is in
> objectaddress.c which isn't a very sensible dependency -- it does seem
> like it would be handy to have a list-based version of construct_array
> moved to arrayfuncs.c but for now it's not much more work to handle
> these ourselves.
>
> I changed the options to accumulate one big array instead of an array
> of bunches of options. Previously you could only end up with a
> singleton array with a comma-delimited string or a two element array
> with one of those and map=.

The changes are fine, this eliminates the unnecessary dependency on
objectaddress.

> I think the error if pg_hba fails to reload needs to be LOG. It would
> be too unexpected to the user who isn't necessarily the one who issued
> the SIGHUP to spontaneously get a warning.
>
> I also removed the "mask" from local entries and made some of the
> NULLS that shouldn't be possible to happen (unknown auth method or
> connection method) actually throw errors.

changes are fine. All the tests are passed.

Out of curiosity, regarding the result materialize code addition, Any
way the caller of "hba_settings" function
"ExecMakeTableFunctionResult" also stores the results in tuple_store.
Is there any advantage
doing it in hba_settings function rather than in the caller?

Regards,
Hari Babu
Fujitsu Australia


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 01:07  Greg Stark <[email protected]>
  parent: Jim Nasby <[email protected]>
  1 sibling, 1 reply; 293+ messages in thread

From: Greg Stark @ 2015-03-04 01:07 UTC (permalink / raw)
  To: Jim Nasby <[email protected]>; +Cc: Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Wed, Mar 4, 2015 at 12:17 AM, Jim Nasby <[email protected]> wrote:
> I can make these changes if you want.

Personally I'm just not convinced this is worth it. It makes the
catalogs harder for people to read and use and only benefits people
who have users named "all" or databases named "all", "sameuser", or
"samerole" which I can't really imagine would be anyone.

If this were going to be the infrastructure on which lots of tools
rested rather than just a read-only view that was mostly going to be
read by humans that might be different. Are you envisioning a tool
that would look at this view, offer a gui for users to make changes
based on that information, and build a new pg_hba.conf to replace it?

-- 
greg


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 01:18  Greg Stark <[email protected]>
  parent: Haribabu Kommi <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Greg Stark @ 2015-03-04 01:18 UTC (permalink / raw)
  To: Haribabu Kommi <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Wed, Mar 4, 2015 at 12:34 AM, Haribabu Kommi
<[email protected]> wrote:
> Out of curiosity, regarding the result materialize code addition, Any
> way the caller of "hba_settings" function
> "ExecMakeTableFunctionResult" also stores the results in tuple_store.
> Is there any advantage
> doing it in hba_settings function rather than in the caller?

That might protect against concurrent pg_hba reloads, though I suspect
there's a CHECK_FOR_INTERRUPTS hanging out in that loop. We could
maybe put one in this loop and check if the parser memory context has
been reset.

But the immediate problem is that having the API that looked up a line
by line number and then calling it repeatedly with successive line
numbers was O(n^2). Each time it was called it had to walk down the
list from the head all over again. 1 + 2 + 3 + 4 + ... n = n(n+1)/2 or
O(n^2). This isn't performance critical but it would not have run in a
reasonable length of time for large pg_hba files.

And having to store the state between calls means the code is at least
as simple for the tuplestore, imho even simpler.

-- 
greg


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 01:35  Haribabu Kommi <[email protected]>
  parent: Greg Stark <[email protected]>
  0 siblings, 2 replies; 293+ messages in thread

From: Haribabu Kommi @ 2015-03-04 01:35 UTC (permalink / raw)
  To: Greg Stark <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Wed, Mar 4, 2015 at 12:18 PM, Greg Stark <[email protected]> wrote:
> On Wed, Mar 4, 2015 at 12:34 AM, Haribabu Kommi
> <[email protected]> wrote:
>> Out of curiosity, regarding the result materialize code addition, Any
>> way the caller of "hba_settings" function
>> "ExecMakeTableFunctionResult" also stores the results in tuple_store.
>> Is there any advantage
>> doing it in hba_settings function rather than in the caller?
>
> That might protect against concurrent pg_hba reloads, though I suspect
> there's a CHECK_FOR_INTERRUPTS hanging out in that loop. We could
> maybe put one in this loop and check if the parser memory context has
> been reset.

I feel there is no problem of current pg_hba reloads, because the
check_for_interrupts
doesn't reload the conf files. It will be done in the "postgresMain"
function once the
query finishes. Am I missing something?

+ foreach(line, parsed_hba_lines)

In the above for loop it is better to add "check_for_interrupts" to
avoid it looping
if the parsed_hba_lines are more.

> But the immediate problem is that having the API that looked up a line
> by line number and then calling it repeatedly with successive line
> numbers was O(n^2). Each time it was called it had to walk down the
> list from the head all over again. 1 + 2 + 3 + 4 + ... n = n(n+1)/2 or
> O(n^2). This isn't performance critical but it would not have run in a
> reasonable length of time for large pg_hba files.
>
> And having to store the state between calls means the code is at least
> as simple for the tuplestore, imho even simpler.

Got it. Thanks.

Regards,
Hari Babu
Fujitsu Australia


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 01:36  Josh Berkus <[email protected]>
  parent: Greg Stark <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Josh Berkus @ 2015-03-04 01:36 UTC (permalink / raw)
  To: pgsql-hackers

On 03/03/2015 05:07 PM, Greg Stark wrote:
> On Wed, Mar 4, 2015 at 12:17 AM, Jim Nasby <[email protected]> wrote:
>> I can make these changes if you want.
> 
> Personally I'm just not convinced this is worth it. It makes the
> catalogs harder for people to read and use and only benefits people
> who have users named "all" or databases named "all", "sameuser", or
> "samerole" which I can't really imagine would be anyone.
> 
> If this were going to be the infrastructure on which lots of tools
> rested rather than just a read-only view that was mostly going to be
> read by humans that might be different. Are you envisioning a tool
> that would look at this view, offer a gui for users to make changes
> based on that information, and build a new pg_hba.conf to replace it?

I'm planning to write such a tool.  However, I am not concerned about
weird name cases like the above.

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 06:34  Haribabu Kommi <[email protected]>
  parent: Haribabu Kommi <[email protected]>
  1 sibling, 1 reply; 293+ messages in thread

From: Haribabu Kommi @ 2015-03-04 06:34 UTC (permalink / raw)
  To: Greg Stark <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Stephen Frost <[email protected]>

On Wed, Mar 4, 2015 at 12:35 PM, Haribabu Kommi
<[email protected]> wrote:
> + foreach(line, parsed_hba_lines)
>
> In the above for loop it is better to add "check_for_interrupts" to
> avoid it looping
> if the parsed_hba_lines are more.

Updated patch is attached with the addition of check_for_interrupts in
the for loop.

Regards,
Hari Babu
Fujitsu Australia


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [application/octet-stream] Catalog_view_to_HBA_settings_patch_V7.patch (19.7K, ../../CAJrrPGfin+RCa_CT_pBMa4u31Aa7kcUOggQ4F1sMX3m5EUV-tA@mail.gmail.com/2-Catalog_view_to_HBA_settings_patch_V7.patch)
  download | inline diff:
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 7393,7398 ****
--- 7393,7403 ----
        <entry>views</entry>
       </row>
  
+      <row>
+       <entry><link linkend="view-pg-hba-settings"><structname>pg_hba_settings</structname></link></entry>
+       <entry>client authentication settings</entry>
+      </row>
+ 
      </tbody>
     </tgroup>
    </table>
***************
*** 9696,9699 **** SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
--- 9701,9786 ----
  
   </sect1>
  
+  <sect1 id="view-pg-hba-settings">
+   <title><structname>pg_hba_settings</structname></title>
+ 
+   <indexterm zone="view-pg-hba-settings">
+    <primary>pg_hba_settings</primary>
+   </indexterm>
+ 
+   <para>
+    The read-only <structname>pg_hba_settings</structname> view provides
+    access to the client authentication configuration from pg_hba.conf.
+    Access to this view is limited to superusers. 
+   </para>
+ 
+   <table>
+    <title><structname>pg_hba_settings</> Columns</title>
+ 
+    <tgroup cols="3">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Type</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><structfield>type</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Type of connection</entry>
+      </row>
+      <row>
+       <entry><structfield>databases</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>List of database names</entry>
+      </row>
+      <row>
+       <entry><structfield>users</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>List of user names</entry>
+      </row>
+      <row>
+       <entry><structfield>address</structfield></entry>
+       <entry><type>inet</type></entry>
+       <entry>Client machine address</entry>
+      </row>
+      <row>
+       <entry><structfield>mask</structfield></entry>
+       <entry><type>inet</type></entry>
+       <entry>IP Mask</entry>
+      </row>
+      <row>
+       <entry><structfield>compare_method</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>IP address comparison method</entry>
+      </row>
+      <row>
+       <entry><structfield>hostname</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Client host name</entry>
+      </row>
+      <row>
+       <entry><structfield>method</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Authentication method</entry>
+      </row>
+      <row>
+       <entry><structfield>options</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>Configuration options set for authentication method</entry>
+      </row>
+      <row>
+       <entry><structfield>line_number</structfield></entry>
+       <entry><type>integer</type></entry>
+       <entry>
+        Line number within client authentication configuration file 
+        the current value was set at
+       </entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </sect1>
  </chapter>
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
***************
*** 680,685 **** local   all             @admins,+support                        md5
--- 680,690 ----
  local   db1,db2,@demodbs  all                                   md5
  </programlisting>
     </example>
+ 
+    <para>
+     The contents of this file are reflected in the pg_hba_settings view.
+     See <xref linkend="view-pg-hba-settings"> for details.
+    </para>
   </sect1>
  
   <sect1 id="auth-username-maps">
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 414,419 **** CREATE RULE pg_settings_n AS
--- 414,424 ----
  
  GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;
  
+ CREATE VIEW pg_hba_settings AS
+     SELECT * FROM pg_hba_settings() AS A;
+ 
+ REVOKE ALL on pg_hba_settings FROM public;
+ 
  CREATE VIEW pg_timezone_abbrevs AS
      SELECT * FROM pg_timezone_abbrevs();
  
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 25,38 ****
--- 25,43 ----
  #include <arpa/inet.h>
  #include <unistd.h>
  
+ #include "access/htup_details.h"
  #include "catalog/pg_collation.h"
+ #include "catalog/pg_type.h"
+ #include "funcapi.h"
  #include "libpq/ip.h"
  #include "libpq/libpq.h"
+ #include "miscadmin.h"
  #include "postmaster/postmaster.h"
  #include "regex/regex.h"
  #include "replication/walsender.h"
  #include "storage/fd.h"
  #include "utils/acl.h"
+ #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
***************
*** 2220,2222 **** hba_getauthmethod(hbaPort *port)
--- 2225,2656 ----
  {
  	check_hba(port);
  }
+ 
+ 
+ /* LDAP supports 10 currently, keep this well above the most any method needs */
+ #define MAX_OPTIONS 16
+ 
+ /*
+  * Fill in suitable values to build a tuple representing the
+  * HbaLine provided
+  */
+ static void
+ hba_getvalues_for_line(HbaLine *hba, Datum *values, bool *nulls)
+ {
+ 	ListCell   *dbcell;
+ 	char		buffer[NI_MAXHOST];
+ 	StringInfoData str;
+ 	int			index = 0;
+ 	int			noptions;
+ 	Datum		options[MAX_OPTIONS];
+ 
+ 	/* connection type */
+ 	switch (hba->conntype)
+ 	{
+ 		case ctLocal:
+ 			values[index] = CStringGetTextDatum("local");
+ 			break;
+ 		case ctHost:
+ 			values[index] = CStringGetTextDatum("host");
+ 			break;
+ 		case ctHostSSL:
+ 			values[index] = CStringGetTextDatum("hostssl");
+ 			break;
+ 		case ctHostNoSSL:
+ 			values[index] = CStringGetTextDatum("hostnossl");
+ 			break;
+ 		default:
+ 			elog(ERROR, "Unexpected Connection Type in parsed HBA entry");
+ 			break;
+ 	}
+ 
+ 	/* databases */
+ 	index++;
+ 	if (list_length(hba->databases) != 0)
+ 	{
+ 		int			j = 0;
+ 		HbaToken   *tok;
+ 		Datum	   *names = palloc(sizeof(Datum) * list_length(hba->databases));
+ 
+ 		foreach(dbcell, hba->databases)
+ 		{
+ 			tok = lfirst(dbcell);
+ 			names[j++] = CStringGetTextDatum(tok->string);
+ 		}
+ 
+ 		values[index] = PointerGetDatum(construct_array(names, list_length(hba->databases),
+ 														TEXTOID, -1, false, 'i'));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* users */
+ 	index++;
+ 	if (list_length(hba->roles) != 0)
+ 	{
+ 		int			j = 0;
+ 		HbaToken   *tok;
+ 		Datum	   *roles = palloc(sizeof(Datum) * list_length(hba->roles));
+ 
+ 		foreach(dbcell, hba->roles)
+ 		{
+ 			tok = lfirst(dbcell);
+ 			roles[j++] = CStringGetTextDatum(tok->string);
+ 		}
+ 
+ 		values[index] = PointerGetDatum(construct_array(roles, list_length(hba->roles),
+ 														TEXTOID, -1, false, 'i'));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* address */
+ 	index++;
+ 	if (pg_getnameinfo_all(&hba->addr, sizeof(struct sockaddr_storage),
+ 						   buffer, sizeof(buffer),
+ 						   NULL, 0,
+ 						   NI_NUMERICHOST) == 0)
+ 	{
+ 		clean_ipv6_addr(hba->addr.ss_family, buffer);
+ 		values[index] = DirectFunctionCall1(inet_in, CStringGetDatum(buffer));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* mask */
+ 	index++;
+ 	if (pg_getnameinfo_all(&hba->mask, sizeof(struct sockaddr_storage),
+ 						   buffer, sizeof(buffer),
+ 						   NULL, 0,
+ 						   NI_NUMERICHOST) == 0)
+ 	{
+ 		clean_ipv6_addr(hba->addr.ss_family, buffer);
+ 		values[index] = DirectFunctionCall1(inet_in, CStringGetDatum(buffer));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* compare method */
+ 	index++;
+ 	if (hba->conntype == ctLocal)
+ 		nulls[index] = true;
+ 	else if (hba->ip_cmp_method == ipCmpMask)
+ 		values[index] = CStringGetTextDatum("mask");
+ 	else if (hba->ip_cmp_method == ipCmpSameHost)
+ 		values[index] = CStringGetTextDatum("samehost");
+ 	else if (hba->ip_cmp_method == ipCmpSameNet)
+ 		values[index] = CStringGetTextDatum("samenet");
+ 	else if (hba->ip_cmp_method == ipCmpAll)
+ 		values[index] = CStringGetTextDatum("all");
+ 	else
+ 		elog(ERROR, "Unexpected Compare Method in parsed HBA entry");
+ 
+ 	/* hostname */
+ 	index++;
+ 	if (hba->hostname)
+ 		values[index] = CStringGetTextDatum(hba->hostname);
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* method */
+ 	index++;
+ 	switch (hba->auth_method)
+ 	{
+ 		case uaReject:
+ 			values[index] = CStringGetTextDatum("reject");
+ 			break;
+ 		case uaImplicitReject:
+ 			values[index] = CStringGetTextDatum("implicitreject");
+ 			break;
+ 		case uaTrust:
+ 			values[index] = CStringGetTextDatum("trust");
+ 			break;
+ 		case uaIdent:
+ 			values[index] = CStringGetTextDatum("ident");
+ 			break;
+ 		case uaPassword:
+ 			values[index] = CStringGetTextDatum("password");
+ 			break;
+ 		case uaMD5:
+ 			values[index] = CStringGetTextDatum("md5");
+ 			break;
+ 		case uaGSS:
+ 			values[index] = CStringGetTextDatum("gss");
+ 			break;
+ 		case uaSSPI:
+ 			values[index] = CStringGetTextDatum("sspi");
+ 			break;
+ 		case uaPAM:
+ 			values[index] = CStringGetTextDatum("pam");
+ 			break;
+ 		case uaLDAP:
+ 			values[index] = CStringGetTextDatum("ldap");
+ 			break;
+ 		case uaCert:
+ 			values[index] = CStringGetTextDatum("cert");
+ 			break;
+ 		case uaRADIUS:
+ 			values[index] = CStringGetTextDatum("radius");
+ 			break;
+ 		case uaPeer:
+ 			values[index] = CStringGetTextDatum("peer");
+ 			break;
+ 		default:
+ 			elog(ERROR, "Unexpected Auth Method in parsed HBA entry");
+ 			break;
+ 	}
+ 
+ 	/* options */
+ 	index++;
+ 	noptions = 0;
+ 
+ 	if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
+ 	{
+ 		if (hba->include_realm)
+ 			options[noptions++] = CStringGetTextDatum("include_realm=true");
+ 
+ 		if (hba->krb_realm)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "krb_realm=");
+ 			appendStringInfoString(&str, hba->krb_realm);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	if (hba->usermap)
+ 	{
+ 		initStringInfo(&str);
+ 		appendStringInfoString(&str, "map=");
+ 		appendStringInfoString(&str, hba->usermap);
+ 		options[noptions++] = CStringGetTextDatum(str.data);
+ 	}
+ 
+ 	if (hba->auth_method == uaLDAP)
+ 	{
+ 		if (hba->ldapserver)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapserver=");
+ 			appendStringInfoString(&str, hba->ldapserver);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapport)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->ldapport);
+ 			appendStringInfoString(&str, "ldapport=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldaptls)
+ 			options[noptions++] = CStringGetTextDatum("ldaptls=true");
+ 
+ 		if (hba->ldapprefix)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapprefix=");
+ 			appendStringInfoString(&str, hba->ldapprefix);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapsuffix)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapsuffix=");
+ 			appendStringInfoString(&str, hba->ldapsuffix);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbasedn)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbasedn=");
+ 			appendStringInfoString(&str, hba->ldapbasedn);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbinddn)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbinddn=");
+ 			appendStringInfoString(&str, hba->ldapbinddn);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbindpasswd)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbindpasswd=");
+ 			appendStringInfoString(&str, hba->ldapbindpasswd);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapsearchattribute)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapsearchattribute=");
+ 			appendStringInfoString(&str, hba->ldapsearchattribute);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapscope)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->ldapscope);
+ 			appendStringInfoString(&str, "ldapscope=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	if (hba->auth_method == uaRADIUS)
+ 	{
+ 		if (hba->radiusserver)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiusserver=");
+ 			appendStringInfoString(&str, hba->radiusserver);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiussecret)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiussecret=");
+ 			appendStringInfoString(&str, hba->radiussecret);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiusidentifier)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiusidentifier=");
+ 			appendStringInfoString(&str, hba->radiusidentifier);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiusport)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->radiusport);
+ 			appendStringInfoString(&str, "radiusport=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	Assert(noptions <= MAX_OPTIONS);
+ 	if (noptions)
+ 		values[index] = PointerGetDatum(
+ 				construct_array(options, noptions, TEXTOID, -1, false, 'i'));
+ 	else
+ 		/* Probably should be {} but that makes for a messy looking view */
+ 		nulls[index] = true;
+ 
+ 	/* line_number */
+ 	index++;
+ 	values[index] = Int32GetDatum(hba->linenumber);
+ }
+ 
+ 
+ #define NUM_PG_HBA_SETTINGS_ATTS   10
+ 
+ /*
+  * SQL-accessible SRF to return all the settings from the pg_hba.conf
+  * file. See the pga_hba_settings view in the "System Catalogs" section of the
+  * manual.
+  */
+ 
+ Datum
+ hba_settings(PG_FUNCTION_ARGS)
+ {
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	tupdesc;
+ 	ListCell   *line;
+ 	MemoryContext old_cxt;
+ 
+ 	/*
+ 	 * We must use the Materialize mode to be safe against HBA file reloads
+ 	 * while the cursor is open. It's also more efficient than having to look
+ 	 * up our current position in the parsed list every time.
+ 	 */
+ 
+ 	ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	/*
+ 	 * Create the tupledesc and tuplestore in the per_query context as
+ 	 * required for SFRM_Materialize.
+ 	 */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	tupdesc = CreateTemplateTupleDesc(NUM_PG_HBA_SETTINGS_ATTS, false);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "type",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "databases",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "users",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "address",
+ 					   INETOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "mask",
+ 					   INETOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "compare_method",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "hostname",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "method",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "options",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "line_number",
+ 					   INT4OID, -1, 0);
+ 	BlessTupleDesc(tupdesc);
+ 
+ 	tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	/*
+ 	 * Loop through the list and deparse each entry as it comes, storing it in
+ 	 * the tuplestore. Any temporary memory allocations here live only for the
+ 	 * function call lifetime.
+ 	 */
+ 	foreach(line, parsed_hba_lines)
+ 	{
+ 		HbaLine    *hba = (HbaLine *) lfirst(line);
+ 		Datum		values[NUM_PG_HBA_SETTINGS_ATTS];
+ 		bool		nulls[NUM_PG_HBA_SETTINGS_ATTS];
+ 		HeapTuple	tuple;
+ 
+ 		MemSet(values, 0, sizeof(values));
+ 		MemSet(nulls, 0, sizeof(nulls));
+ 
+ 		CHECK_FOR_INTERRUPTS();
+ 
+ 		/* Get the next parsed hba line values */
+ 		hba_getvalues_for_line(hba, values, nulls);
+ 
+ 		/* build a tuple */
+ 		tuple = heap_form_tuple(tupdesc, values, nulls);
+ 		tuplestore_puttuple(tuple_store, tuple);
+ 	}
+ 
+ 	rsi->setDesc = tupdesc;
+ 	rsi->setResult = tuple_store;
+ 
+ 	PG_RETURN_NULL();
+ }
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
***************
*** 3996,4001 **** PostgresMain(int argc, char *argv[],
--- 3996,4009 ----
  		{
  			got_SIGHUP = false;
  			ProcessConfigFile(PGC_SIGHUP);
+ 			
+ 			/* 
+ 			 * Reload authentication config files too to refresh 
+ 			 * pg_hba_settings view data.
+ 			 */
+ 			if (!load_hba())
+ 				ereport(LOG,
+ 					(errmsg("pg_hba.conf not reloaded, pg_hba_settings may show stale information")));
  		}
  
  		/*
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 3023,3028 **** DATA(insert OID = 2078 (  set_config		PGNSP PGUID 12 1 0 0 0 f f f f f f v 3 0 2
--- 3023,3030 ----
  DESCR("SET X as a function");
  DATA(insert OID = 2084 (  pg_show_all_settings	PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,25,25,25,25,25,25,25,25,25,25,1009,25,25,25,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{name,setting,unit,category,short_desc,extra_desc,context,vartype,source,min_val,max_val,enumvals,boot_val,reset_val,sourcefile,sourceline}" _null_ show_all_settings _null_ _null_ _null_ ));
  DESCR("SHOW ALL as a function");
+ DATA(insert OID = 3582 (  pg_hba_settings  PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,1009,1009,869,869,25,25,25,1009,23}" "{o,o,o,o,o,o,o,o,o,o}" "{type,databases,users,address,mask,compare_method,hostname,method,options,line_number}" _null_ hba_settings _null_ _null_ _null_ ));
+ DESCR("view client authentication settings");
  DATA(insert OID = 1371 (  pg_lock_status   PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}" _null_ pg_lock_status _null_ _null_ _null_ ));
  DESCR("view system lock information");
  DATA(insert OID = 1065 (  pg_prepared_xact PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{28,25,1184,26,26}" "{o,o,o,o,o}" "{transaction,gid,prepared,ownerid,dbid}" _null_ pg_prepared_xact _null_ _null_ _null_ ));
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 1090,1095 **** extern Datum quote_nullable(PG_FUNCTION_ARGS);
--- 1090,1096 ----
  extern Datum show_config_by_name(PG_FUNCTION_ARGS);
  extern Datum set_config_by_name(PG_FUNCTION_ARGS);
  extern Datum show_all_settings(PG_FUNCTION_ARGS);
+ extern Datum hba_settings(PG_FUNCTION_ARGS);
  
  /* lockfuncs.c */
  extern Datum pg_lock_status(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
***************
*** 1315,1320 **** pg_group| SELECT pg_authid.rolname AS groname,
--- 1315,1331 ----
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
    WHERE (NOT pg_authid.rolcanlogin);
+ pg_hba_settings| SELECT a.type,
+     a.databases,
+     a.users,
+     a.address,
+     a.mask,
+     a.compare_method,
+     a.hostname,
+     a.method,
+     a.options,
+     a.line_number
+    FROM pg_hba_settings() a(type, databases, users, address, mask, compare_method, hostname, method, options, line_number);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 12:32  Greg Stark <[email protected]>
  parent: Haribabu Kommi <[email protected]>
  1 sibling, 0 replies; 293+ messages in thread

From: Greg Stark @ 2015-03-04 12:32 UTC (permalink / raw)
  To: Haribabu Kommi <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Wed, Mar 4, 2015 at 1:35 AM, Haribabu Kommi <[email protected]> wrote:
> I feel there is no problem of current pg_hba reloads, because the
> check_for_interrupts
> doesn't reload the conf files. It will be done in the "postgresMain"
> function once the
> query finishes. Am I missing something?

Ah, no I guess that's right. I even noticed that earlier but forgot.

-- 
greg


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-04 21:41  Peter Eisentraut <[email protected]>
  parent: Jim Nasby <[email protected]>
  1 sibling, 2 replies; 293+ messages in thread

From: Peter Eisentraut @ 2015-03-04 21:41 UTC (permalink / raw)
  To: Jim Nasby <[email protected]>; Greg Stark <[email protected]>; +Cc: Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On 3/3/15 7:17 PM, Jim Nasby wrote:
> I think we're screwed in that regard anyway, because of the special
> constructs. You'd need different logic to handle things like +role and
> sameuser. We might even end up painted in a corner where we can't change
> it in the future because it'll break everyone's scripts.

Yeah, I'm getting worried about this.  I think most people agree that
getting a peek at pg_hba.conf from within the server is useful, but
everyone seems to have quite different uses for it.  Greg wants to join
against other catalog tables, Jim wants to reassemble a valid and
accurate pg_hba.conf, Josh wants to write an editing tool.  Personally,
I'd like to see something as close to the actual file as possible.

If there were an obviously correct way to map the various special
constructs to the available SQL data types, then fine.  But if there
isn't, then we shouldn't give a false overinterpretation.  So I'd render
everything that's disputed as a plain text field.  (Not even an array of
text.)


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-05 12:46  Pavel Stehule <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  1 sibling, 0 replies; 293+ messages in thread

From: Pavel Stehule @ 2015-03-05 12:46 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Jim Nasby <[email protected]>; Greg Stark <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

2015-03-04 22:41 GMT+01:00 Peter Eisentraut <[email protected]>:

> On 3/3/15 7:17 PM, Jim Nasby wrote:
> > I think we're screwed in that regard anyway, because of the special
> > constructs. You'd need different logic to handle things like +role and
> > sameuser. We might even end up painted in a corner where we can't change
> > it in the future because it'll break everyone's scripts.
>
> Yeah, I'm getting worried about this.  I think most people agree that
> getting a peek at pg_hba.conf from within the server is useful, but
> everyone seems to have quite different uses for it.  Greg wants to join
> against other catalog tables, Jim wants to reassemble a valid and
> accurate pg_hba.conf, Josh wants to write an editing tool.  Personally,
> I'd like to see something as close to the actual file as possible.
>
> If there were an obviously correct way to map the various special
> constructs to the available SQL data types, then fine.  But if there
> isn't, then we shouldn't give a false overinterpretation.  So I'd render
> everything that's disputed as a plain text field.  (Not even an array of
> text.)
>

I disagree with last note - arrays where is expected are valid. I don't see
any reason why anybody have to do parsing some informations from any table.

The face of pg_hba.conf in SQL space can be different than original file -
but all data should be clean (without necessity of additional parsing)

Regards

Pavel


>
>
> --
> Sent via pgsql-hackers mailing list ([email protected])
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-05 14:42  Greg Stark <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  1 sibling, 1 reply; 293+ messages in thread

From: Greg Stark @ 2015-03-05 14:42 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Wed, Mar 4, 2015 at 9:41 PM, Peter Eisentraut <[email protected]> wrote:
> everyone seems to have quite different uses for it.  Greg wants to join
> against other catalog tables, Jim wants to reassemble a valid and
> accurate pg_hba.conf, Josh wants to write an editing tool.  Personally,
> I'd like to see something as close to the actual file as possible.

Well if you want to read the file as is you can do so using the file
reading functions which afaik were specifically intended for the
purpose of writing config editing tools.


Just to repeat, even if you're reassembling a valid and accurage
pg_hba.conf or writing an editing tool you can do that with what we
have today as long as there are no databases named "all", "sameuser",
or "samerole" and no roles named "all". That's something an editing
tool could check and provide a warning for to the user and refuse to
write a config file if it's the case and I doubt there are any such
users out there anyways.

Having five separate columns would work but I just think it's way more
complicated than necessary and burdens everyone who wants to use the
view less formally.

>
> If there were an obviously correct way to map the various special
> constructs to the available SQL data types, then fine.  But if there
> isn't, then we shouldn't give a false overinterpretation.  So I'd render
> everything that's disputed as a plain text field.  (Not even an array of
> text.)

Joining against other catalog tables may be kind of exaggerated but if
I have data in an SQL view I certainly expect to be able to write
WHERE clauses to find the rows I want without having to do string
parsing. If it were a comma-delimited string I have to do something
like WHERE users LIKE '%,username,%' but then that's not good enough
since it may be the first or last and the username itself may contain
white-space or a comma etc etc.

I think knowing about the + prefix and the risk of tokens like "all"
and "sameuser" etc are pretty small compromises to make. But having to
know the quoting rules and write a tokenizer are too far.



-- 
greg


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-06 03:11  Peter Eisentraut <[email protected]>
  parent: Greg Stark <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Peter Eisentraut @ 2015-03-06 03:11 UTC (permalink / raw)
  To: Greg Stark <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On 3/5/15 9:42 AM, Greg Stark wrote:
> Well if you want to read the file as is you can do so using the file
> reading functions which afaik were specifically intended for the
> purpose of writing config editing tools.

Sure, but those things are almost never installed by default, and I
don't want to install them if they provide easy write access, and they
don't help you find the actual file.  This proposal is much nicer.

> Joining against other catalog tables may be kind of exaggerated but if
> I have data in an SQL view I certainly expect to be able to write
> WHERE clauses to find the rows I want without having to do string
> parsing. If it were a comma-delimited string I have to do something
> like WHERE users LIKE '%,username,%' but then that's not good enough
> since it may be the first or last and the username itself may contain
> white-space or a comma etc etc.

The point is, it should be one or the other (or both), not something in
the middle.

It's either a textual representation of the file or a semantic one.  If
it's the latter, then all user names, group names, and special key words
need to be resolved in some way that they cannot be confused with
unfortunately named user names.  And there needs to be a way that we can
add more in the future.



-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-11 17:32  Greg Stark <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Greg Stark @ 2015-03-11 17:32 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Fri, Mar 6, 2015 at 3:11 AM, Peter Eisentraut <[email protected]> wrote:

> The point is, it should be one or the other (or both), not something in
> the middle.
>
> It's either a textual representation of the file or a semantic one.  If
> it's the latter, then all user names, group names, and special key words
> need to be resolved in some way that they cannot be confused with
> unfortunately named user names.  And there needs to be a way that we can
> add more in the future.
>

I think what we have here is already a good semantic representation. It
doesn't handle all the corner cases but those corner cases are a) very
unlikely and b) easy to check for. A tool can check for any users starting
with + or named "all" or any databases called "sameuser" or "samerole". If
they exist then the view isn't good enough to reconstruct the raw file. But
they're very unlikely to exist, I've never heard of anyone with such things
and can't imagine why someone would make them.

The view as is is useful for everyone. Someone who wants to write a tool
can reconstruct the file as long as none of the problematic names exist in
the database. Someone who wants to run audits can run queries to see what
users have access to or check the permissions on a database and do simple
comparisons to check for whatever they want to check for.

If we change it to be 100% unambiguous then it will be cumbersome to work
with. The view will have 5 columns instead of 2 dedicated to users and
databases. It will be much harder for humans to read, and for people
writing code they'll have to check three columns for matches. It just seems
like a lot of busywork to make it less useful.

Attached are two copies of the view, one as is today and one with the
keywords and recursive elements broken out.

Also, fwiw as far as adding more in the future -- breaking the keywords out
into a separate column wouldn't interfere with that but if we want to
include any additional syntax like + then it will require additional
columns. That would actually be less flexible for future changes.

I'm inclined to commit this as is.


-- 
greg

::***# select distinct type,databases,users,address,mask,compare_method,hostname,method,options  from pg_hba_settings;
┌───────┬───────────────────────────────────┬───────────────────────────────────────┬───────────────┬─────────────────────────────┬────────────────┬──────────────┬────────┬───────────────────────────┐
│ type  │             databases             │                 users                 │    address    │            mask             │ compare_method │   hostname   │ method │          options          │
├───────┼───────────────────────────────────┼───────────────────────────────────────┼───────────────┼─────────────────────────────┼────────────────┼──────────────┼────────┼───────────────────────────┤
│ host  │ {all}                             │ {all}                                 │ ::1           │ ffff:ffff:ffff:ffff:ffff:ff…│ mask           │              │ trust  │                           │
│       │                                   │                                       │               │…ff:ffff:ffff                │                │              │        │                           │
│ host  │ {all}                             │ {all}                                 │ 127.0.0.1     │ 255.255.255.255             │ mask           │              │ trust  │                           │
│ local │ {all}                             │ {adminfoo,adminbar,adminbaz,+support} │               │                             │                │              │ md5    │                           │
│ local │ {quoted}                          │ {all}                                 │               │                             │                │              │ trust  │                           │
│ host  │ {samehostdb}                      │ {all}                                 │               │                             │ samehost       │              │ trust  │                           │
│ host  │ {samenetdb}                       │ {all}                                 │               │                             │ samenet        │              │ trust  │                           │
│ local │ {postgres}                        │ {+stark}                              │               │                             │                │              │ trust  │                           │
│ local │ {db1,db2,demofoo,demobar,demobaz} │ {all}                                 │               │                             │                │              │ md5    │                           │
│ local │ {postgres}                        │ {stark}                               │               │                             │                │              │ trust  │                           │
│ local │ {all}                             │ {all}                                 │               │                             │                │              │ radius │ {radiusserver=localhost,r…│
│       │                                   │                                       │               │                             │                │              │        │…adiussecret=bar,"radiusid…│
│       │                                   │                                       │               │                             │                │              │        │…entifier=baz baz baz",rad…│
│       │                                   │                                       │               │                             │                │              │        │…iusport=999}              │
│ local │ {all}                             │ {adminfoo,adminbar,adminbaz}          │               │                             │                │              │ md5    │                           │
│ local │ {replication,all,alsdkf}          │ {all}                                 │               │                             │                │              │ trust  │                           │
│ host  │ {all}                             │ {all}                                 │               │                             │ mask           │ .example.com │ md5    │                           │
│ local │ {postgres,slkdjfsl}               │ {stark,sldkfs}                        │               │                             │                │              │ trust  │                           │
│ local │ {sameuser}                        │ {all}                                 │               │                             │                │              │ md5    │                           │
│ host  │ {all}                             │ {all}                                 │               │                             │ mask           │ localhost    │ trust  │                           │
│ host  │ {postgres}                        │ {all}                                 │ 192.168.12.10 │ 255.255.255.255             │ mask           │              │ md5    │                           │
│ local │ {all}                             │ {all}                                 │               │                             │                │              │ trust  │                           │
│ local │ {all}                             │ {quoted}                              │               │                             │                │              │ trust  │                           │
│ local │ {replication}                     │ {replication}                         │               │                             │                │              │ trust  │                           │
│ host  │ {all}                             │ {all}                                 │ 192.168.54.1  │ 255.255.255.255             │ mask           │              │ reject │                           │
│ local │ {sameuser}                        │ {all}                                 │               │                             │                │              │ trust  │                           │
│ local │ {all}                             │ {+support}                            │               │                             │                │              │ md5    │                           │
│ host  │ {postgres}                        │ {all}                                 │ 192.168.93.0  │ 255.255.255.0               │ mask           │              │ ident  │                           │
│ host  │ {all}                             │ {all}                                 │ 192.168.0.0   │ 255.255.0.0                 │ mask           │              │ ident  │ {map=omicron}             │
└───────┴───────────────────────────────────┴───────────────────────────────────────┴───────────────┴─────────────────────────────┴────────────────┴──────────────┴────────┴───────────────────────────┘
(25 rows)

Time: 128.471 ms


::***# select type,keyword_databases,databases,keyword_users,users,recursive_users,address,mask,compare_method,hostname,method,options from pg_hba;
┌───────┬───────────────────┬────────────────────────────┬───────────────┬─────────────────────┬─────────────────┬──────────┬─────────────────┬────────────────┬──────────────┬────────┬───────────────┐
│ type  │ keyword_databases │         databases          │ keyword_users │        users        │ recursive_users │ address  │      mask       │ compare_method │   hostname   │ method │    options    │
├───────┼───────────────────┼────────────────────────────┼───────────────┼─────────────────────┼─────────────────┼──────────┼─────────────────┼────────────────┼──────────────┼────────┼───────────────┤
│ local │ {sameuser}        │ {}                         │ {all}         │ {}                  │                 │          │                 │                │              │ md5    │               │
│ local │ {sameuser}        │ {}                         │ {all}         │ {}                  │                 │          │                 │                │              │ trust  │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │ ::1      │ ffff:ffff:ffff:…│ mask           │              │ trust  │               │
│       │                   │                            │               │                     │                 │          │…ffff:ffff:ffff:…│                │              │        │               │
│       │                   │                            │               │                     │                 │          │…ffff:ffff       │                │              │        │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │ 127.0.0.…│ 255.255.255.255 │ mask           │              │ trust  │               │
│       │                   │                            │               │                     │                 │…1        │                 │                │              │        │               │
│ local │ {all}             │ {}                         │ {all}         │ {}                  │                 │          │                 │                │              │ radius │ {radiusserver…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…=localhost,ra…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…diussecret=ba…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…r,"radiusiden…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…tifier=baz ba…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…z baz",radius…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…port=999}     │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │          │                 │ mask           │ .example.com │ md5    │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │          │                 │ mask           │ localhost    │ trust  │               │
│ local │ {all}             │ {}                         │ {all}         │ {}                  │                 │          │                 │                │              │ trust  │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │ 192.168.…│ 255.255.255.255 │ mask           │              │ reject │               │
│       │                   │                            │               │                     │                 │…54.1     │                 │                │              │        │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │ 192.168.…│ 255.255.0.0     │ mask           │              │ ident  │ {map=omicron} │
│       │                   │                            │               │                     │                 │…0.0      │                 │                │              │        │               │
│ local │ {}                │ {quoted}                   │ {all}         │ {}                  │                 │          │                 │                │              │ trust  │               │
│ host  │ {}                │ {samehostdb}               │ {all}         │ {}                  │                 │          │                 │ samehost       │              │ trust  │               │
│ host  │ {}                │ {samenetdb}                │ {all}         │ {}                  │                 │          │                 │ samenet        │              │ trust  │               │
│ local │ {}                │ {db1,db2,demofoo,demobar,d…│ {all}         │ {}                  │                 │          │                 │                │              │ md5    │               │
│       │                   │…emobaz}                    │               │                     │                 │          │                 │                │              │        │               │
│ local │ {}                │ {replication,all,alsdkf}   │ {all}         │ {}                  │                 │          │                 │                │              │ trust  │               │
│ host  │ {}                │ {postgres}                 │ {all}         │ {}                  │                 │ 192.168.…│ 255.255.255.255 │ mask           │              │ md5    │               │
│       │                   │                            │               │                     │                 │…12.10    │                 │                │              │        │               │
│ host  │ {}                │ {postgres}                 │ {all}         │ {}                  │                 │ 192.168.…│ 255.255.255.0   │ mask           │              │ ident  │               │
│       │                   │                            │               │                     │                 │…93.0     │                 │                │              │        │               │
│ local │ {all}             │ {}                         │ {}            │ {}                  │ {support}       │          │                 │                │              │ md5    │               │
│ local │ {all}             │ {}                         │ {}            │ {adminfoo,adminbar,…│ {support}       │          │                 │                │              │ md5    │               │
│       │                   │                            │               │…adminbaz}           │                 │          │                 │                │              │        │               │
│ local │ {all}             │ {}                         │ {}            │ {adminfoo,adminbar,…│                 │          │                 │                │              │ md5    │               │
│       │                   │                            │               │…adminbaz}           │                 │          │                 │                │              │        │               │
│ local │ {}                │ {postgres}                 │ {}            │ {stark}             │                 │          │                 │                │              │ trust  │               │
│ local │ {all}             │ {}                         │ {}            │ {quoted}            │                 │          │                 │                │              │ trust  │               │
│ local │ {}                │ {postgres,slkdjfsl}        │ {}            │ {stark,sldkfs}      │                 │          │                 │                │              │ trust  │               │
│ local │ {}                │ {replication}              │ {}            │ {replication}       │                 │          │                 │                │              │ trust  │               │
│ local │ {}                │ {postgres}                 │ {}            │ {}                  │ {stark}         │          │                 │                │              │ trust  │               │
└───────┴───────────────────┴────────────────────────────┴───────────────┴─────────────────────┴─────────────────┴──────────┴─────────────────┴────────────────┴──────────────┴────────┴───────────────┘
(25 rows)

Time: 11.431 ms


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [text/plain] pg_hba_settings.txt (19.7K, ../../CAM-w4HMj69+f_aLRpJT+37598dz2eGFG3ge4=-4qt_dwtNh=nw@mail.gmail.com/3-pg_hba_settings.txt)
  download | inline:
::***# select distinct type,databases,users,address,mask,compare_method,hostname,method,options  from pg_hba_settings;
┌───────┬───────────────────────────────────┬───────────────────────────────────────┬───────────────┬─────────────────────────────┬────────────────┬──────────────┬────────┬───────────────────────────┐
│ type  │             databases             │                 users                 │    address    │            mask             │ compare_method │   hostname   │ method │          options          │
├───────┼───────────────────────────────────┼───────────────────────────────────────┼───────────────┼─────────────────────────────┼────────────────┼──────────────┼────────┼───────────────────────────┤
│ host  │ {all}                             │ {all}                                 │ ::1           │ ffff:ffff:ffff:ffff:ffff:ff…│ mask           │              │ trust  │                           │
│       │                                   │                                       │               │…ff:ffff:ffff                │                │              │        │                           │
│ host  │ {all}                             │ {all}                                 │ 127.0.0.1     │ 255.255.255.255             │ mask           │              │ trust  │                           │
│ local │ {all}                             │ {adminfoo,adminbar,adminbaz,+support} │               │                             │                │              │ md5    │                           │
│ local │ {quoted}                          │ {all}                                 │               │                             │                │              │ trust  │                           │
│ host  │ {samehostdb}                      │ {all}                                 │               │                             │ samehost       │              │ trust  │                           │
│ host  │ {samenetdb}                       │ {all}                                 │               │                             │ samenet        │              │ trust  │                           │
│ local │ {postgres}                        │ {+stark}                              │               │                             │                │              │ trust  │                           │
│ local │ {db1,db2,demofoo,demobar,demobaz} │ {all}                                 │               │                             │                │              │ md5    │                           │
│ local │ {postgres}                        │ {stark}                               │               │                             │                │              │ trust  │                           │
│ local │ {all}                             │ {all}                                 │               │                             │                │              │ radius │ {radiusserver=localhost,r…│
│       │                                   │                                       │               │                             │                │              │        │…adiussecret=bar,"radiusid…│
│       │                                   │                                       │               │                             │                │              │        │…entifier=baz baz baz",rad…│
│       │                                   │                                       │               │                             │                │              │        │…iusport=999}              │
│ local │ {all}                             │ {adminfoo,adminbar,adminbaz}          │               │                             │                │              │ md5    │                           │
│ local │ {replication,all,alsdkf}          │ {all}                                 │               │                             │                │              │ trust  │                           │
│ host  │ {all}                             │ {all}                                 │               │                             │ mask           │ .example.com │ md5    │                           │
│ local │ {postgres,slkdjfsl}               │ {stark,sldkfs}                        │               │                             │                │              │ trust  │                           │
│ local │ {sameuser}                        │ {all}                                 │               │                             │                │              │ md5    │                           │
│ host  │ {all}                             │ {all}                                 │               │                             │ mask           │ localhost    │ trust  │                           │
│ host  │ {postgres}                        │ {all}                                 │ 192.168.12.10 │ 255.255.255.255             │ mask           │              │ md5    │                           │
│ local │ {all}                             │ {all}                                 │               │                             │                │              │ trust  │                           │
│ local │ {all}                             │ {quoted}                              │               │                             │                │              │ trust  │                           │
│ local │ {replication}                     │ {replication}                         │               │                             │                │              │ trust  │                           │
│ host  │ {all}                             │ {all}                                 │ 192.168.54.1  │ 255.255.255.255             │ mask           │              │ reject │                           │
│ local │ {sameuser}                        │ {all}                                 │               │                             │                │              │ trust  │                           │
│ local │ {all}                             │ {+support}                            │               │                             │                │              │ md5    │                           │
│ host  │ {postgres}                        │ {all}                                 │ 192.168.93.0  │ 255.255.255.0               │ mask           │              │ ident  │                           │
│ host  │ {all}                             │ {all}                                 │ 192.168.0.0   │ 255.255.0.0                 │ mask           │              │ ident  │ {map=omicron}             │
└───────┴───────────────────────────────────┴───────────────────────────────────────┴───────────────┴─────────────────────────────┴────────────────┴──────────────┴────────┴───────────────────────────┘
(25 rows)

Time: 128.471 ms


::***# select type,keyword_databases,databases,keyword_users,users,recursive_users,address,mask,compare_method,hostname,method,options from pg_hba;
┌───────┬───────────────────┬────────────────────────────┬───────────────┬─────────────────────┬─────────────────┬──────────┬─────────────────┬────────────────┬──────────────┬────────┬───────────────┐
│ type  │ keyword_databases │         databases          │ keyword_users │        users        │ recursive_users │ address  │      mask       │ compare_method │   hostname   │ method │    options    │
├───────┼───────────────────┼────────────────────────────┼───────────────┼─────────────────────┼─────────────────┼──────────┼─────────────────┼────────────────┼──────────────┼────────┼───────────────┤
│ local │ {sameuser}        │ {}                         │ {all}         │ {}                  │                 │          │                 │                │              │ md5    │               │
│ local │ {sameuser}        │ {}                         │ {all}         │ {}                  │                 │          │                 │                │              │ trust  │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │ ::1      │ ffff:ffff:ffff:…│ mask           │              │ trust  │               │
│       │                   │                            │               │                     │                 │          │…ffff:ffff:ffff:…│                │              │        │               │
│       │                   │                            │               │                     │                 │          │…ffff:ffff       │                │              │        │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │ 127.0.0.…│ 255.255.255.255 │ mask           │              │ trust  │               │
│       │                   │                            │               │                     │                 │…1        │                 │                │              │        │               │
│ local │ {all}             │ {}                         │ {all}         │ {}                  │                 │          │                 │                │              │ radius │ {radiusserver…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…=localhost,ra…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…diussecret=ba…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…r,"radiusiden…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…tifier=baz ba…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…z baz",radius…│
│       │                   │                            │               │                     │                 │          │                 │                │              │        │…port=999}     │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │          │                 │ mask           │ .example.com │ md5    │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │          │                 │ mask           │ localhost    │ trust  │               │
│ local │ {all}             │ {}                         │ {all}         │ {}                  │                 │          │                 │                │              │ trust  │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │ 192.168.…│ 255.255.255.255 │ mask           │              │ reject │               │
│       │                   │                            │               │                     │                 │…54.1     │                 │                │              │        │               │
│ host  │ {all}             │ {}                         │ {all}         │ {}                  │                 │ 192.168.…│ 255.255.0.0     │ mask           │              │ ident  │ {map=omicron} │
│       │                   │                            │               │                     │                 │…0.0      │                 │                │              │        │               │
│ local │ {}                │ {quoted}                   │ {all}         │ {}                  │                 │          │                 │                │              │ trust  │               │
│ host  │ {}                │ {samehostdb}               │ {all}         │ {}                  │                 │          │                 │ samehost       │              │ trust  │               │
│ host  │ {}                │ {samenetdb}                │ {all}         │ {}                  │                 │          │                 │ samenet        │              │ trust  │               │
│ local │ {}                │ {db1,db2,demofoo,demobar,d…│ {all}         │ {}                  │                 │          │                 │                │              │ md5    │               │
│       │                   │…emobaz}                    │               │                     │                 │          │                 │                │              │        │               │
│ local │ {}                │ {replication,all,alsdkf}   │ {all}         │ {}                  │                 │          │                 │                │              │ trust  │               │
│ host  │ {}                │ {postgres}                 │ {all}         │ {}                  │                 │ 192.168.…│ 255.255.255.255 │ mask           │              │ md5    │               │
│       │                   │                            │               │                     │                 │…12.10    │                 │                │              │        │               │
│ host  │ {}                │ {postgres}                 │ {all}         │ {}                  │                 │ 192.168.…│ 255.255.255.0   │ mask           │              │ ident  │               │
│       │                   │                            │               │                     │                 │…93.0     │                 │                │              │        │               │
│ local │ {all}             │ {}                         │ {}            │ {}                  │ {support}       │          │                 │                │              │ md5    │               │
│ local │ {all}             │ {}                         │ {}            │ {adminfoo,adminbar,…│ {support}       │          │                 │                │              │ md5    │               │
│       │                   │                            │               │…adminbaz}           │                 │          │                 │                │              │        │               │
│ local │ {all}             │ {}                         │ {}            │ {adminfoo,adminbar,…│                 │          │                 │                │              │ md5    │               │
│       │                   │                            │               │…adminbaz}           │                 │          │                 │                │              │        │               │
│ local │ {}                │ {postgres}                 │ {}            │ {stark}             │                 │          │                 │                │              │ trust  │               │
│ local │ {all}             │ {}                         │ {}            │ {quoted}            │                 │          │                 │                │              │ trust  │               │
│ local │ {}                │ {postgres,slkdjfsl}        │ {}            │ {stark,sldkfs}      │                 │          │                 │                │              │ trust  │               │
│ local │ {}                │ {replication}              │ {}            │ {replication}       │                 │          │                 │                │              │ trust  │               │
│ local │ {}                │ {postgres}                 │ {}            │ {}                  │ {stark}         │          │                 │                │              │ trust  │               │
└───────┴───────────────────┴────────────────────────────┴───────────────┴─────────────────────┴─────────────────┴──────────┴─────────────────┴────────────────┴──────────────┴────────┴───────────────┘
(25 rows)

Time: 11.431 ms

^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-13 02:33  Peter Eisentraut <[email protected]>
  parent: Haribabu Kommi <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Peter Eisentraut @ 2015-03-13 02:33 UTC (permalink / raw)
  To: Haribabu Kommi <[email protected]>; Greg Stark <[email protected]>; +Cc: Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Stephen Frost <[email protected]>

On 3/4/15 1:34 AM, Haribabu Kommi wrote:
> On Wed, Mar 4, 2015 at 12:35 PM, Haribabu Kommi
> <[email protected]> wrote:
>> + foreach(line, parsed_hba_lines)
>>
>> In the above for loop it is better to add "check_for_interrupts" to
>> avoid it looping
>> if the parsed_hba_lines are more.
> 
> Updated patch is attached with the addition of check_for_interrupts in
> the for loop.

I tried out your latest patch.  I like that it updates even in running
sessions when the file is reloaded.

The permission checking is faulty, because unprivileged users can
execute pg_hba_settings() directly.

Check the error messages against the style guide (especially
capitalization).

I don't like that there is a hard-coded limit of 16 options 5 pages away
from where it is actually used.  That could be done better.

I'm not sure about the name "pg_hba_settings".  Why not just "pg_hba" or
"pg_hba_conf" if you're going for a representation that is close to the
file (as opposed to pg_settings, which is really a lot more abstract
than any particular file).

I would put the line_number column first.

I continue to think that it is a serious mistake to stick special values
like 'all' and 'replication' into the arrays without additional
decoration.  That makes this view useless or at least dangerous for
editing tools or tools that want to reassemble the original file.
Clearly at least one of those has to be a use case.  Otherwise we can
just print out the physical lines without interpretation.

The "mask" field can go, because address is of type inet, which can
represent masks itself.  (Or should it be cidr then?  Who knows.)  The
preferred visual representation of masks in pg_hba.conf has been
"address/mask" for a while now, so we should preserve that.
Additionally, you can then use the existing inet/cidr operations to do
things like checking whether some IP address is contained in an address
specification.

I can't tell from the documentation what the "compare_method" field is
supposed to do.  I see it on the code, but that is not a natural
representation of pg_hba.conf.  In fact, this just supports my earlier
statement.  Why are special values in the address field special, but not
in the user or database fields?

uaImplicitReject is not a user-facing authentication method, so it
shouldn't be shown (or showable).

I would have expected options to be split into keys and values.

All that code to reassemble the options from the parsed struct
representation seems crazy to me.  Surely, we could save the strings as
we parse them?

I can't make sense of the log message "pg_hba.conf not reloaded,
pg_hba_settings may show stale information".  If load_hba() failed, the
information isn't stale, is it?

In any case, printing this to the server log seems kind of pointless,
because someone using the view is presumably doing so because they can't
or don't want to read the server log.  The proper place might be a
warning when the view is actually called.



-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-16 15:57  Robert Haas <[email protected]>
  parent: Greg Stark <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Robert Haas @ 2015-03-16 15:57 UTC (permalink / raw)
  To: Greg Stark <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Wed, Mar 11, 2015 at 1:32 PM, Greg Stark <[email protected]> wrote:
> I think what we have here is already a good semantic representation. It
> doesn't handle all the corner cases but those corner cases are a) very
> unlikely and b) easy to check for. A tool can check for any users starting
> with + or named "all" or any databases called "sameuser" or "samerole". If
> they exist then the view isn't good enough to reconstruct the raw file. But
> they're very unlikely to exist, I've never heard of anyone with such things
> and can't imagine why someone would make them.

-1.  Like Peter, I think this is a bad plan.  Somebody looking at the
view should be able to understand with 100% confidence, and without
additional parsing, what the semantics of the pg_hba.conf file are.
Saying "those cases are unlikely so we're not going to handle them" is
really selling ourselves short.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-16 16:29  Alvaro Herrera <[email protected]>
  parent: Robert Haas <[email protected]>
  0 siblings, 2 replies; 293+ messages in thread

From: Alvaro Herrera @ 2015-03-16 16:29 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Greg Stark <[email protected]>; Peter Eisentraut <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

Robert Haas wrote:
> On Wed, Mar 11, 2015 at 1:32 PM, Greg Stark <[email protected]> wrote:
> > I think what we have here is already a good semantic representation. It
> > doesn't handle all the corner cases but those corner cases are a) very
> > unlikely and b) easy to check for. A tool can check for any users starting
> > with + or named "all" or any databases called "sameuser" or "samerole". If
> > they exist then the view isn't good enough to reconstruct the raw file. But
> > they're very unlikely to exist, I've never heard of anyone with such things
> > and can't imagine why someone would make them.
> 
> -1.  Like Peter, I think this is a bad plan.  Somebody looking at the
> view should be able to understand with 100% confidence, and without
> additional parsing, what the semantics of the pg_hba.conf file are.
> Saying "those cases are unlikely so we're not going to handle them" is
> really selling ourselves short.

+1 what Robert said.  I think the additional "keyword" columns are a
good solution to the issue.

-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-16 16:49  Greg Stark <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 0 replies; 293+ messages in thread

From: Greg Stark @ 2015-03-16 16:49 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Robert Haas <[email protected]>; Peter Eisentraut <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Mon, Mar 16, 2015 at 4:29 PM, Alvaro Herrera <[email protected]>
wrote:

> +1 what Robert said.  I think the additional "keyword" columns are a
> good solution to the issue.
>

Huh. Well I disagree but obviously I'm in the minority. I'll put fix it up
accordingly today and post the resulting view output (which I expect will
look like the example in the previous message). I think it's cumbersome but
it shouldn't be hard to implement.


-- 
greg


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-16 17:46  David G. Johnston <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 2 replies; 293+ messages in thread

From: David G. Johnston @ 2015-03-16 17:46 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Robert Haas <[email protected]>; Greg Stark <[email protected]>; Peter Eisentraut <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Mon, Mar 16, 2015 at 9:29 AM, Alvaro Herrera <[email protected]>
wrote:

> Robert Haas wrote:
> > On Wed, Mar 11, 2015 at 1:32 PM, Greg Stark <[email protected]> wrote:
> > > I think what we have here is already a good semantic representation. It
> > > doesn't handle all the corner cases but those corner cases are a) very
> > > unlikely and b) easy to check for. A tool can check for any users
> starting
> > > with + or named "all" or any databases called "sameuser" or
> "samerole". If
> > > they exist then the view isn't good enough to reconstruct the raw
> file. But
> > > they're very unlikely to exist, I've never heard of anyone with such
> things
> > > and can't imagine why someone would make them.
> >
> > -1.  Like Peter, I think this is a bad plan.  Somebody looking at the
> > view should be able to understand with 100% confidence, and without
> > additional parsing, what the semantics of the pg_hba.conf file are.
> > Saying "those cases are unlikely so we're not going to handle them" is
> > really selling ourselves short.
>
> +1 what Robert said.  I think the additional "keyword" columns are a
> good solution to the issue.
>
>
​Why not just leave the double-quoting requirements intact.  An unquoted
<any> or <sameuser> (etc) would represent the special keyword while the
quoted version would mean that the name is used literally.

​Have the: "​A separate file containing [database|user] names can be
specified by preceding the file name with @" possibilities been added to
the test suite?

I'm not totally opposed to using some other quoting symbol to represent
keywords as well - like "*" (e.g., *all*).  Like Greg, I'm not to keen on
the idea of adding additional keyword columns.

*Logical View - Keyword Expansion*

​My other thought was to not use the keywords at all and simply resolve
their meaning​ to actual role/database names and list them explicitly.
That would be a true logical view and allow for simple user checking by
saying: 'username' = ANY(users); without the need for ([...] OR '*any*' =
ANY(users) or similar).  If going that route I guess it would behoove us to
either incorporate a "physical" view of the actual file converted to a
table or to then maybe have some kind of "tags" fields with the special
values encoded should someone want to know how the user list was
generated.  In effect, the "pg_hba" view in the original file but with
actual names of users and databases instead of empty arrays.

The "sameuser" => "all" pairing is a pure physical representation in that
instance.  What I would do in a logical representation is have a single
record for each user that has a database of the same name in the current
database.  However, because of that limitation you either would be
duplicating information by keeping "sameuser,all" or you would have to have
a separate view representing the physical view of the file.  I think having
two views, one logical and one physical, would solve the two use cases
nicely without having to compromise or incorporate too much redundancy and
complexity.

Likewise, if we know the host ip address and subnet mask the keywords
"samehost" and "samenet" should be resolvable to actual values at the time
of viewing.  Though that would add the complication of multi-network
listening...


I guess a full logical view is a bit much but if we keep the same quoting
mechanics as mandated by the file then there should be no ambiguity in
terms of label meaning and whomever is looking at this stuff has to
understand about the keywords and using quote to remove the special-ness
anyway.

David J.


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-16 18:08  Robert Haas <[email protected]>
  parent: David G. Johnston <[email protected]>
  1 sibling, 0 replies; 293+ messages in thread

From: Robert Haas @ 2015-03-16 18:08 UTC (permalink / raw)
  To: David G. Johnston <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Greg Stark <[email protected]>; Peter Eisentraut <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Mon, Mar 16, 2015 at 1:46 PM, David G. Johnston
<[email protected]> wrote:
> Why not just leave the double-quoting requirements intact.  An unquoted
> <any> or <sameuser> (etc) would represent the special keyword while the
> quoted version would mean that the name is used literally.

That would be OK with me, I think.

> I'm not totally opposed to using some other quoting symbol to represent
> keywords as well - like "*" (e.g., *all*).  Like Greg, I'm not to keen on
> the idea of adding additional keyword columns.

We probably should not use one quoting convention in the file and
another in the view.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-16 18:11  Greg Stark <[email protected]>
  parent: David G. Johnston <[email protected]>
  1 sibling, 1 reply; 293+ messages in thread

From: Greg Stark @ 2015-03-16 18:11 UTC (permalink / raw)
  To: David G. Johnston <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Robert Haas <[email protected]>; Peter Eisentraut <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Mon, Mar 16, 2015 at 5:46 PM, David G. Johnston <
[email protected]> wrote:

> ​Why not just leave the double-quoting requirements intact.  An unquoted
> <any> or <sameuser> (etc) would represent the special keyword while the
> quoted version would mean that the name is used literally.
>

For users that would be worse than not quoting. Then if they look up users
they can't say WHERE username =ANY (users). They would have to do
sumersaults like CASE WHEN username = 'all' then '"all"' =ANY (users) else
username =ALL (users).

The whole point of having a view should be that you don't need to know the
syntax rules for pg_hba.conf to interpret the data. If you do then you
might as well just write a parser and read the file.


-- 
greg


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-16 19:23  David G. Johnston <[email protected]>
  parent: Greg Stark <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: David G. Johnston @ 2015-03-16 19:23 UTC (permalink / raw)
  To: Greg Stark <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Robert Haas <[email protected]>; Peter Eisentraut <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Prabakaran, Vaishnavi <[email protected]>; Magnus Hagander <[email protected]>; Jaime Casanova <[email protected]>

On Mon, Mar 16, 2015 at 11:11 AM, Greg Stark <[email protected]> wrote:

>
> On Mon, Mar 16, 2015 at 5:46 PM, David G. Johnston <
> [email protected]> wrote:
>
>> ​Why not just leave the double-quoting requirements intact.  An unquoted
>> <any> or <sameuser> (etc) would represent the special keyword while the
>> quoted version would mean that the name is used literally.
>>
>
> For users that would be worse than not quoting. Then if they look up users
> they can't say WHERE username =ANY (users). They would have to do
> sumersaults like CASE WHEN username = 'all' then '"all"' =ANY (users) else
> username =ALL (users).
>
> The whole point of having a view should be that you don't need to know the
> syntax rules for pg_hba.conf to interpret the data. If you do then you
> might as well just write a parser and read the file.
>
>
>
​Create a "pg_hba_user" type, and an implicit cast from text to that type,
so when you say: "WHERE 'any' = ANY(...)" the system does the syntax
conversion for you and the user doesn't have to, for the most part, be
aware of the special rules for quoting names.  Otherwise I don't see how
you can meet the requirement to accommodate "any" as a valid user
identifier​

​without using two columns - one for keywords and one for users using the
quoting rules of PostgreSQL pg_role instead of using the, more restrictive,
rules of pg_hba.conf

​​
​
​In that case I would not leave the users column with an empty array when
"any" is specified but would incorporate all known roles into the value;
though maybe it is just noise during typical usage...it would likely be a
long field that would be useful for querying but not necessarily for
display.

​David J.​
​


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-27 08:23  Haribabu Kommi <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Haribabu Kommi @ 2015-03-27 08:23 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: Greg Stark <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Stephen Frost <[email protected]>

On Fri, Mar 13, 2015 at 1:33 PM, Peter Eisentraut <[email protected]> wrote:
> On 3/4/15 1:34 AM, Haribabu Kommi wrote:
>> On Wed, Mar 4, 2015 at 12:35 PM, Haribabu Kommi
>> <[email protected]> wrote:
>>> + foreach(line, parsed_hba_lines)
>>>
>>> In the above for loop it is better to add "check_for_interrupts" to
>>> avoid it looping
>>> if the parsed_hba_lines are more.
>>
>> Updated patch is attached with the addition of check_for_interrupts in
>> the for loop.
>
> I tried out your latest patch.  I like that it updates even in running
> sessions when the file is reloaded.

Thanks for the review. Sorry for late reply.

> The permission checking is faulty, because unprivileged users can
> execute pg_hba_settings() directly.

corrected.

> Check the error messages against the style guide (especially
> capitalization).

corrected.

> I don't like that there is a hard-coded limit of 16 options 5 pages away
> from where it is actually used.  That could be done better.

changed to 12 instead of 16.

> I'm not sure about the name "pg_hba_settings".  Why not just "pg_hba" or
> "pg_hba_conf" if you're going for a representation that is close to the
> file (as opposed to pg_settings, which is really a lot more abstract
> than any particular file).

changed to pg_hba_conf.

> I would put the line_number column first.

changed.

> I continue to think that it is a serious mistake to stick special values
> like 'all' and 'replication' into the arrays without additional
> decoration.  That makes this view useless or at least dangerous for
> editing tools or tools that want to reassemble the original file.
> Clearly at least one of those has to be a use case.  Otherwise we can
> just print out the physical lines without interpretation.

It is possible to provide more than one keyword for databases or users.
Is it fine to use the text array for keyword databases and keyword users.

> The "mask" field can go, because address is of type inet, which can
> represent masks itself.  (Or should it be cidr then?  Who knows.)  The
> preferred visual representation of masks in pg_hba.conf has been
> "address/mask" for a while now, so we should preserve that.
> Additionally, you can then use the existing inet/cidr operations to do
> things like checking whether some IP address is contained in an address
> specification.

removed.

> I can't tell from the documentation what the "compare_method" field is
> supposed to do.  I see it on the code, but that is not a natural
> representation of pg_hba.conf.  In fact, this just supports my earlier
> statement.  Why are special values in the address field special, but not
> in the user or database fields?
>
> uaImplicitReject is not a user-facing authentication method, so it
> shouldn't be shown (or showable).

removed.

> I would have expected options to be split into keys and values.
>
> All that code to reassemble the options from the parsed struct
> representation seems crazy to me.  Surely, we could save the strings as
> we parse them?

I didn't get this point clearly. Can you explain it a bit more.

> I can't make sense of the log message "pg_hba.conf not reloaded,
> pg_hba_settings may show stale information".  If load_hba() failed, the
> information isn't stale, is it?
>
> In any case, printing this to the server log seems kind of pointless,
> because someone using the view is presumably doing so because they can't
> or don't want to read the server log.  The proper place might be a
> warning when the view is actually called.

Changed accordingly as if the reload fails, further selects on the
view through a warning
as view data may not be proper like below.

"There was some failure in reloading pg_hba.conf file. The pg_hba.conf
settings data may contains stale information"

Here I attached latest patch with the fixes other than keyword columns.
I will provide the patch with keyword columns and documentation changes later.

Regards,
Hari Babu
Fujitsu Australia


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [application/octet-stream] Catalog_view_to_HBA_settings_patch_V8.patch (20.2K, ../../CAJrrPGe5Du0wmELzhWF0N-iiy6j5N0WNrhOhPuT5o5-bSCP6gA@mail.gmail.com/2-Catalog_view_to_HBA_settings_patch_V8.patch)
  download | inline diff:
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 7384,7389 ****
--- 7384,7394 ----
        <entry>views</entry>
       </row>
  
+      <row>
+       <entry><link linkend="view-pg-hba-settings"><structname>pg_hba_settings</structname></link></entry>
+       <entry>client authentication settings</entry>
+      </row>
+ 
      </tbody>
     </tgroup>
    </table>
***************
*** 9682,9685 **** SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
--- 9687,9772 ----
  
   </sect1>
  
+  <sect1 id="view-pg-hba-settings">
+   <title><structname>pg_hba_settings</structname></title>
+ 
+   <indexterm zone="view-pg-hba-settings">
+    <primary>pg_hba_settings</primary>
+   </indexterm>
+ 
+   <para>
+    The read-only <structname>pg_hba_settings</structname> view provides
+    access to the client authentication configuration from pg_hba.conf.
+    Access to this view is limited to superusers. 
+   </para>
+ 
+   <table>
+    <title><structname>pg_hba_settings</> Columns</title>
+ 
+    <tgroup cols="3">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Type</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><structfield>type</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Type of connection</entry>
+      </row>
+      <row>
+       <entry><structfield>databases</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>List of database names</entry>
+      </row>
+      <row>
+       <entry><structfield>users</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>List of user names</entry>
+      </row>
+      <row>
+       <entry><structfield>address</structfield></entry>
+       <entry><type>inet</type></entry>
+       <entry>Client machine address</entry>
+      </row>
+      <row>
+       <entry><structfield>mask</structfield></entry>
+       <entry><type>inet</type></entry>
+       <entry>IP Mask</entry>
+      </row>
+      <row>
+       <entry><structfield>compare_method</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>IP address comparison method</entry>
+      </row>
+      <row>
+       <entry><structfield>hostname</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Client host name</entry>
+      </row>
+      <row>
+       <entry><structfield>method</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Authentication method</entry>
+      </row>
+      <row>
+       <entry><structfield>options</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>Configuration options set for authentication method</entry>
+      </row>
+      <row>
+       <entry><structfield>line_number</structfield></entry>
+       <entry><type>integer</type></entry>
+       <entry>
+        Line number within client authentication configuration file 
+        the current value was set at
+       </entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </sect1>
  </chapter>
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
***************
*** 680,685 **** local   all             @admins,+support                        md5
--- 680,690 ----
  local   db1,db2,@demodbs  all                                   md5
  </programlisting>
     </example>
+ 
+    <para>
+     The contents of this file are reflected in the pg_hba_settings view.
+     See <xref linkend="view-pg-hba-settings"> for details.
+    </para>
   </sect1>
  
   <sect1 id="auth-username-maps">
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 411,416 **** CREATE RULE pg_settings_n AS
--- 411,419 ----
  
  GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;
  
+ CREATE VIEW pg_hba_conf AS
+     SELECT * FROM pg_hba_conf() AS A;
+ 
  CREATE VIEW pg_timezone_abbrevs AS
      SELECT * FROM pg_timezone_abbrevs();
  
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 25,38 ****
--- 25,43 ----
  #include <arpa/inet.h>
  #include <unistd.h>
  
+ #include "access/htup_details.h"
  #include "catalog/pg_collation.h"
+ #include "catalog/pg_type.h"
+ #include "funcapi.h"
  #include "libpq/ip.h"
  #include "libpq/libpq.h"
+ #include "miscadmin.h"
  #include "postmaster/postmaster.h"
  #include "regex/regex.h"
  #include "replication/walsender.h"
  #include "storage/fd.h"
  #include "utils/acl.h"
+ #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
***************
*** 74,79 **** typedef struct HbaToken
--- 79,87 ----
  	bool		quoted;
  } HbaToken;
  
+ /* Flag to indicate the failure of reloading pg_hba.conf file */
+ bool load_hba_failure = false;
+ 
  /*
   * pre-parsed content of HBA config file: list of HbaLine structs.
   * parsed_hba_context is the memory context where it lives.
***************
*** 2220,2222 **** hba_getauthmethod(hbaPort *port)
--- 2228,2650 ----
  {
  	check_hba(port);
  }
+ 
+ 
+ /* LDAP supports 10 currently, keep this well above the most any method needs */
+ #define MAX_OPTIONS 12
+ 
+ /*
+  * Fill in suitable values to build a tuple representing the
+  * HbaLine provided
+  */
+ static void
+ hba_getvalues_for_line(HbaLine *hba, Datum *values, bool *nulls)
+ {
+ 	ListCell   *dbcell;
+ 	char		buffer[NI_MAXHOST];
+ 	StringInfoData str;
+ 	int			index = 0;
+ 	int			noptions;
+ 	Datum		options[MAX_OPTIONS];
+ 
+ 	/* line_number */
+ 	values[index] = Int32GetDatum(hba->linenumber);
+ 	
+ 	/* connection type */
+ 	index++;
+ 	switch (hba->conntype)
+ 	{
+ 		case ctLocal:
+ 			values[index] = CStringGetTextDatum("local");
+ 			break;
+ 		case ctHost:
+ 			values[index] = CStringGetTextDatum("host");
+ 			break;
+ 		case ctHostSSL:
+ 			values[index] = CStringGetTextDatum("hostssl");
+ 			break;
+ 		case ctHostNoSSL:
+ 			values[index] = CStringGetTextDatum("hostnossl");
+ 			break;
+ 		default:
+ 			elog(ERROR, "unexpected connection type in parsed HBA entry");
+ 			break;
+ 	}
+ 
+ 	/* databases */
+ 	index++;
+ 	if (list_length(hba->databases) != 0)
+ 	{
+ 		int			j = 0;
+ 		HbaToken   *tok;
+ 		Datum	   *names = palloc(sizeof(Datum) * list_length(hba->databases));
+ 
+ 		foreach(dbcell, hba->databases)
+ 		{
+ 			tok = lfirst(dbcell);
+ 			names[j++] = CStringGetTextDatum(tok->string);
+ 		}
+ 
+ 		values[index] = PointerGetDatum(construct_array(names, list_length(hba->databases),
+ 														TEXTOID, -1, false, 'i'));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* users */
+ 	index++;
+ 	if (list_length(hba->roles) != 0)
+ 	{
+ 		int			j = 0;
+ 		HbaToken   *tok;
+ 		Datum	   *roles = palloc(sizeof(Datum) * list_length(hba->roles));
+ 
+ 		foreach(dbcell, hba->roles)
+ 		{
+ 			tok = lfirst(dbcell);
+ 			roles[j++] = CStringGetTextDatum(tok->string);
+ 		}
+ 
+ 		values[index] = PointerGetDatum(construct_array(roles, list_length(hba->roles),
+ 														TEXTOID, -1, false, 'i'));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* address */
+ 	index++;
+ 	if (pg_getnameinfo_all(&hba->addr, sizeof(struct sockaddr_storage),
+ 						   buffer, sizeof(buffer),
+ 						   NULL, 0,
+ 						   NI_NUMERICHOST) == 0)
+ 	{
+ 		clean_ipv6_addr(hba->addr.ss_family, buffer);
+ 		values[index] = DirectFunctionCall1(inet_in, CStringGetDatum(buffer));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* compare method */
+ 	index++;
+ 	if (hba->conntype == ctLocal)
+ 		nulls[index] = true;
+ 	else if (hba->ip_cmp_method == ipCmpMask)
+ 		values[index] = CStringGetTextDatum("mask");
+ 	else if (hba->ip_cmp_method == ipCmpSameHost)
+ 		values[index] = CStringGetTextDatum("samehost");
+ 	else if (hba->ip_cmp_method == ipCmpSameNet)
+ 		values[index] = CStringGetTextDatum("samenet");
+ 	else if (hba->ip_cmp_method == ipCmpAll)
+ 		values[index] = CStringGetTextDatum("all");
+ 	else
+ 		elog(ERROR, "unexpected compare method in parsed HBA entry");
+ 
+ 	/* hostname */
+ 	index++;
+ 	if (hba->hostname)
+ 		values[index] = CStringGetTextDatum(hba->hostname);
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* method */
+ 	index++;
+ 	switch (hba->auth_method)
+ 	{
+ 		case uaReject:
+ 			values[index] = CStringGetTextDatum("reject");
+ 			break;
+ 		case uaTrust:
+ 			values[index] = CStringGetTextDatum("trust");
+ 			break;
+ 		case uaIdent:
+ 			values[index] = CStringGetTextDatum("ident");
+ 			break;
+ 		case uaPassword:
+ 			values[index] = CStringGetTextDatum("password");
+ 			break;
+ 		case uaMD5:
+ 			values[index] = CStringGetTextDatum("md5");
+ 			break;
+ 		case uaGSS:
+ 			values[index] = CStringGetTextDatum("gss");
+ 			break;
+ 		case uaSSPI:
+ 			values[index] = CStringGetTextDatum("sspi");
+ 			break;
+ 		case uaPAM:
+ 			values[index] = CStringGetTextDatum("pam");
+ 			break;
+ 		case uaLDAP:
+ 			values[index] = CStringGetTextDatum("ldap");
+ 			break;
+ 		case uaCert:
+ 			values[index] = CStringGetTextDatum("cert");
+ 			break;
+ 		case uaRADIUS:
+ 			values[index] = CStringGetTextDatum("radius");
+ 			break;
+ 		case uaPeer:
+ 			values[index] = CStringGetTextDatum("peer");
+ 			break;
+ 		default:
+ 			elog(ERROR, "unexpected authentication method in parsed HBA entry");
+ 			break;
+ 	}
+ 
+ 	/* options */
+ 	index++;
+ 	noptions = 0;
+ 
+ 	if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
+ 	{
+ 		if (hba->include_realm)
+ 			options[noptions++] = CStringGetTextDatum("include_realm=true");
+ 
+ 		if (hba->krb_realm)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "krb_realm=");
+ 			appendStringInfoString(&str, hba->krb_realm);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	if (hba->usermap)
+ 	{
+ 		initStringInfo(&str);
+ 		appendStringInfoString(&str, "map=");
+ 		appendStringInfoString(&str, hba->usermap);
+ 		options[noptions++] = CStringGetTextDatum(str.data);
+ 	}
+ 
+ 	if (hba->auth_method == uaLDAP)
+ 	{
+ 		if (hba->ldapserver)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapserver=");
+ 			appendStringInfoString(&str, hba->ldapserver);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapport)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->ldapport);
+ 			appendStringInfoString(&str, "ldapport=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldaptls)
+ 			options[noptions++] = CStringGetTextDatum("ldaptls=true");
+ 
+ 		if (hba->ldapprefix)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapprefix=");
+ 			appendStringInfoString(&str, hba->ldapprefix);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapsuffix)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapsuffix=");
+ 			appendStringInfoString(&str, hba->ldapsuffix);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbasedn)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbasedn=");
+ 			appendStringInfoString(&str, hba->ldapbasedn);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbinddn)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbinddn=");
+ 			appendStringInfoString(&str, hba->ldapbinddn);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbindpasswd)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbindpasswd=");
+ 			appendStringInfoString(&str, hba->ldapbindpasswd);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapsearchattribute)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapsearchattribute=");
+ 			appendStringInfoString(&str, hba->ldapsearchattribute);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapscope)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->ldapscope);
+ 			appendStringInfoString(&str, "ldapscope=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	if (hba->auth_method == uaRADIUS)
+ 	{
+ 		if (hba->radiusserver)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiusserver=");
+ 			appendStringInfoString(&str, hba->radiusserver);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiussecret)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiussecret=");
+ 			appendStringInfoString(&str, hba->radiussecret);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiusidentifier)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiusidentifier=");
+ 			appendStringInfoString(&str, hba->radiusidentifier);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiusport)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->radiusport);
+ 			appendStringInfoString(&str, "radiusport=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	Assert(noptions <= MAX_OPTIONS);
+ 	if (noptions)
+ 		values[index] = PointerGetDatum(
+ 				construct_array(options, noptions, TEXTOID, -1, false, 'i'));
+ 	else
+ 		/* Probably should be {} but that makes for a messy looking view */
+ 		nulls[index] = true;
+ }
+ 
+ 
+ #define NUM_PG_HBA_CONF_ATTS   9
+ 
+ /*
+  * SQL-accessible SRF to return all the settings from the pg_hba.conf
+  * file. See the pg_hba_conf view in the "System Catalogs" section of the
+  * manual.
+  */
+ Datum
+ hba_conf(PG_FUNCTION_ARGS)
+ {
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	tupdesc;
+ 	ListCell   *line;
+ 	MemoryContext old_cxt;
+ 
+ 	/*
+ 	 * We must use the Materialize mode to be safe against HBA file reloads
+ 	 * while the cursor is open. It's also more efficient than having to look
+ 	 * up our current position in the parsed list every time.
+ 	 */
+ 
+ 	ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!superuser())
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 			  (errmsg("only superuser can view pg_hba.conf settings"))));
+ 
+ 	if (load_hba_failure)
+ 		ereport(WARNING,
+  			  (errmsg("There was some failure in reloading pg_hba.conf file. "
+ 					  "The pg_hba.conf settings data may contains stale information")));
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	/*
+ 	 * Create the tupledesc and tuplestore in the per_query context as
+ 	 * required for SFRM_Materialize.
+ 	 */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	tupdesc = CreateTemplateTupleDesc(NUM_PG_HBA_CONF_ATTS, false);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "line_number",
+ 					   INT4OID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "databases",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "users",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "address",
+ 					   INETOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "compare_method",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "hostname",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "method",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "options",
+ 					   TEXTARRAYOID, -1, 0);
+ 	BlessTupleDesc(tupdesc);
+ 
+ 	tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	/*
+ 	 * Loop through the list and deparse each entry as it comes, storing it in
+ 	 * the tuplestore. Any temporary memory allocations here live only for the
+ 	 * function call lifetime.
+ 	 */
+ 	foreach(line, parsed_hba_lines)
+ 	{
+ 		HbaLine    *hba = (HbaLine *) lfirst(line);
+ 		Datum		values[NUM_PG_HBA_CONF_ATTS];
+ 		bool		nulls[NUM_PG_HBA_CONF_ATTS];
+ 		HeapTuple	tuple;
+ 
+ 		MemSet(values, 0, sizeof(values));
+ 		MemSet(nulls, 0, sizeof(nulls));
+ 
+ 		CHECK_FOR_INTERRUPTS();
+ 
+ 		/* Get the next parsed hba line values */
+ 		hba_getvalues_for_line(hba, values, nulls);
+ 
+ 		/* build a tuple */
+ 		tuple = heap_form_tuple(tupdesc, values, nulls);
+ 		tuplestore_puttuple(tuple_store, tuple);
+ 	}
+ 
+ 	rsi->setDesc = tupdesc;
+ 	rsi->setResult = tuple_store;
+ 
+ 	PG_RETURN_NULL();
+ }
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
***************
*** 3996,4001 **** PostgresMain(int argc, char *argv[],
--- 3996,4014 ----
  		{
  			got_SIGHUP = false;
  			ProcessConfigFile(PGC_SIGHUP);
+ 			
+ 			/* 
+ 			 * Reload authentication config files too to refresh 
+ 			 * pg_hba_conf view data.
+ 			 */
+ 			if (!load_hba())
+ 			{
+ 				ereport(DEBUG1,
+ 					(errmsg("Falure in reloading pg_hba.conf, pg_hba_conf view may show stale information")));
+ 				load_hba_failure = true;
+ 			}
+ 			
+ 			load_hba_failure = false;
  		}
  
  		/*
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 3046,3051 **** DATA(insert OID = 2078 (  set_config		PGNSP PGUID 12 1 0 0 0 f f f f f f v 3 0 2
--- 3046,3053 ----
  DESCR("SET X as a function");
  DATA(insert OID = 2084 (  pg_show_all_settings	PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,25,25,25,25,25,25,25,25,25,25,1009,25,25,25,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{name,setting,unit,category,short_desc,extra_desc,context,vartype,source,min_val,max_val,enumvals,boot_val,reset_val,sourcefile,sourceline}" _null_ show_all_settings _null_ _null_ _null_ ));
  DESCR("SHOW ALL as a function");
+ DATA(insert OID = 3582 (  pg_hba_conf  PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{23,25,1009,1009,869,25,25,25,1009}" "{o,o,o,o,o,o,o,o,o}" "{line_number,type,databases,users,address,compare_method,hostname,method,options}" _null_ hba_conf _null_ _null_ _null_ ));
+ DESCR("view client authentication settings");
  DATA(insert OID = 1371 (  pg_lock_status   PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}" _null_ pg_lock_status _null_ _null_ _null_ ));
  DESCR("view system lock information");
  DATA(insert OID = 1065 (  pg_prepared_xact PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{28,25,1184,26,26}" "{o,o,o,o,o}" "{transaction,gid,prepared,ownerid,dbid}" _null_ pg_prepared_xact _null_ _null_ _null_ ));
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 96,101 **** typedef struct IdentLine
--- 96,103 ----
  /* kluge to avoid including libpq/libpq-be.h here */
  typedef struct Port hbaPort;
  
+ extern bool load_hba_failure;
+ 
  extern bool load_hba(void);
  extern bool load_ident(void);
  extern void hba_getauthmethod(hbaPort *port);
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 1097,1102 **** extern Datum quote_nullable(PG_FUNCTION_ARGS);
--- 1097,1103 ----
  extern Datum show_config_by_name(PG_FUNCTION_ARGS);
  extern Datum set_config_by_name(PG_FUNCTION_ARGS);
  extern Datum show_all_settings(PG_FUNCTION_ARGS);
+ extern Datum hba_conf(PG_FUNCTION_ARGS);
  
  /* lockfuncs.c */
  extern Datum pg_lock_status(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
***************
*** 1315,1320 **** pg_group| SELECT pg_authid.rolname AS groname,
--- 1315,1331 ----
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
    WHERE (NOT pg_authid.rolcanlogin);
+ pg_hba_settings| SELECT a.type,
+     a.databases,
+     a.users,
+     a.address,
+     a.mask,
+     a.compare_method,
+     a.hostname,
+     a.method,
+     a.options,
+     a.line_number
+    FROM pg_hba_settings() a(type, databases, users, address, mask, compare_method, hostname, method, options, line_number);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-29 17:34  Pavel Stehule <[email protected]>
  parent: Haribabu Kommi <[email protected]>
  0 siblings, 1 reply; 293+ messages in thread

From: Pavel Stehule @ 2015-03-29 17:34 UTC (permalink / raw)
  To: Haribabu Kommi <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Greg Stark <[email protected]>; Jim Nasby <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers; Stephen Frost <[email protected]>

Hi

I checked this patch. I like the functionality and behave.

There is minor issue with outdated regress test

test rules                    ... FAILED

I have no objections.

Regards

Pavel



2015-03-27 9:23 GMT+01:00 Haribabu Kommi <[email protected]>:

> On Fri, Mar 13, 2015 at 1:33 PM, Peter Eisentraut <[email protected]> wrote:
> > On 3/4/15 1:34 AM, Haribabu Kommi wrote:
> >> On Wed, Mar 4, 2015 at 12:35 PM, Haribabu Kommi
> >> <[email protected]> wrote:
> >>> + foreach(line, parsed_hba_lines)
> >>>
> >>> In the above for loop it is better to add "check_for_interrupts" to
> >>> avoid it looping
> >>> if the parsed_hba_lines are more.
> >>
> >> Updated patch is attached with the addition of check_for_interrupts in
> >> the for loop.
> >
> > I tried out your latest patch.  I like that it updates even in running
> > sessions when the file is reloaded.
>
> Thanks for the review. Sorry for late reply.
>
> > The permission checking is faulty, because unprivileged users can
> > execute pg_hba_settings() directly.
>
> corrected.
>
> > Check the error messages against the style guide (especially
> > capitalization).
>
> corrected.
>
> > I don't like that there is a hard-coded limit of 16 options 5 pages away
> > from where it is actually used.  That could be done better.
>
> changed to 12 instead of 16.
>
> > I'm not sure about the name "pg_hba_settings".  Why not just "pg_hba" or
> > "pg_hba_conf" if you're going for a representation that is close to the
> > file (as opposed to pg_settings, which is really a lot more abstract
> > than any particular file).
>
> changed to pg_hba_conf.
>
> > I would put the line_number column first.
>
> changed.
>
> > I continue to think that it is a serious mistake to stick special values
> > like 'all' and 'replication' into the arrays without additional
> > decoration.  That makes this view useless or at least dangerous for
> > editing tools or tools that want to reassemble the original file.
> > Clearly at least one of those has to be a use case.  Otherwise we can
> > just print out the physical lines without interpretation.
>
> It is possible to provide more than one keyword for databases or users.
> Is it fine to use the text array for keyword databases and keyword users.
>
> > The "mask" field can go, because address is of type inet, which can
> > represent masks itself.  (Or should it be cidr then?  Who knows.)  The
> > preferred visual representation of masks in pg_hba.conf has been
> > "address/mask" for a while now, so we should preserve that.
> > Additionally, you can then use the existing inet/cidr operations to do
> > things like checking whether some IP address is contained in an address
> > specification.
>
> removed.
>
> > I can't tell from the documentation what the "compare_method" field is
> > supposed to do.  I see it on the code, but that is not a natural
> > representation of pg_hba.conf.  In fact, this just supports my earlier
> > statement.  Why are special values in the address field special, but not
> > in the user or database fields?
> >
> > uaImplicitReject is not a user-facing authentication method, so it
> > shouldn't be shown (or showable).
>
> removed.
>
> > I would have expected options to be split into keys and values.
> >
> > All that code to reassemble the options from the parsed struct
> > representation seems crazy to me.  Surely, we could save the strings as
> > we parse them?
>
> I didn't get this point clearly. Can you explain it a bit more.
>
> > I can't make sense of the log message "pg_hba.conf not reloaded,
> > pg_hba_settings may show stale information".  If load_hba() failed, the
> > information isn't stale, is it?
> >
> > In any case, printing this to the server log seems kind of pointless,
> > because someone using the view is presumably doing so because they can't
> > or don't want to read the server log.  The proper place might be a
> > warning when the view is actually called.
>
> Changed accordingly as if the reload fails, further selects on the
> view through a warning
> as view data may not be proper like below.
>
> "There was some failure in reloading pg_hba.conf file. The pg_hba.conf
> settings data may contains stale information"
>
> Here I attached latest patch with the fixes other than keyword columns.
> I will provide the patch with keyword columns and documentation changes
> later.
>
> Regards,
> Hari Babu
> Fujitsu Australia
>
>
> --
> Sent via pgsql-hackers mailing list ([email protected])
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* Re: Providing catalog view to pg_hba.conf file - Patch submission
@ 2015-03-31 12:38  Haribabu Kommi <[email protected]>
  parent: Pavel Stehule <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Haribabu Kommi @ 2015-03-31 12:38 UTC (permalink / raw)
  To: Pavel Stehule <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Greg Stark <[email protected]>; Abhijit Menon-Sen <[email protected]>; pgsql-hackers

On Mon, Mar 30, 2015 at 4:34 AM, Pavel Stehule <[email protected]> wrote:
> Hi
>
> I checked this patch. I like the functionality and behave.

Thanks for the review.

Here I attached updated patch with the following changes.

1. Addition of two new keyword columns

keyword_databases - The database name can be "all", "replication",
sameuser", "samerole" and "samegroup".
keyword_roles - The role can be "all" and a group name prefixed with "+".

The rest of the database and role names are treated as normal database
and role names.

2. Added the code changes to identify the names with quoted.

3. Updated documentation changes

4. Regression test is corrected.


Regards,
Hari Babu
Fujitsu Australia


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [application/octet-stream] Catalog_view_to_HBA_settings_patch_V9.patch (22.9K, ../../CAJrrPGect-xLu+cmq9RN1EeGXvMeMJAtjWNxasKdyUjJKt0ksQ@mail.gmail.com/2-Catalog_view_to_HBA_settings_patch_V9.patch)
  download | inline diff:
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 7384,7389 ****
--- 7384,7394 ----
        <entry>views</entry>
       </row>
  
+      <row>
+       <entry><link linkend="view-pg-hba-conf"><structname>pg_hba_conf</structname></link></entry>
+       <entry>client authentication settings</entry>
+      </row>
+ 
      </tbody>
     </tgroup>
    </table>
***************
*** 9682,9685 **** SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
--- 9687,9783 ----
  
   </sect1>
  
+  <sect1 id="view-pg-hba-conf">
+   <title><structname>pg_hba_conf</structname></title>
+ 
+   <indexterm zone="view-pg-hba-conf">
+    <primary>pg_hba_conf</primary>
+   </indexterm>
+ 
+   <para>
+    The read-only <structname>pg_hba_conf</structname> view provides
+    access to the client authentication configuration from pg_hba.conf.
+    Access to this view is limited to superusers. 
+   </para>
+ 
+   <table>
+    <title><structname>pg_hba_conf</> Columns</title>
+ 
+    <tgroup cols="3">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Type</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><structfield>line_number</structfield></entry>
+       <entry><type>integer</type></entry>
+       <entry>
+        Line number within client authentication configuration file 
+        the current value was set at
+       </entry>
+      </row>
+      <row>
+       <entry><structfield>type</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Type of connection</entry>
+      </row>
+      <row>
+       <entry><structfield>keyword_databases</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>
+        List of keyword database names,
+        name can be all, sameuser, samerole, replication and samegroup
+       </entry>
+      </row>
+      <row>
+       <entry><structfield>databases</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>List of database names</entry>
+      </row>
+      <row>
+       <entry><structfield>keyword_users</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>
+        List of keyword user names,
+        name can be all and a group name prefixed with "+"
+       </entry>
+      </row>
+      <row>
+       <entry><structfield>users</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>List of user names</entry>
+      </row>
+      <row>
+       <entry><structfield>address</structfield></entry>
+       <entry><type>inet</type></entry>
+       <entry>Client machine address</entry>
+      </row>
+      <row>
+       <entry><structfield>compare_method</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>IP address comparison method</entry>
+      </row>
+      <row>
+       <entry><structfield>hostname</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Client host name</entry>
+      </row>
+      <row>
+       <entry><structfield>method</structfield></entry>
+       <entry><type>text</type></entry>
+       <entry>Authentication method</entry>
+      </row>
+      <row>
+       <entry><structfield>options</structfield></entry>
+       <entry><type>text[]</type></entry>
+       <entry>Configuration options set for authentication method</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+  </sect1>
  </chapter>
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
***************
*** 680,685 **** local   all             @admins,+support                        md5
--- 680,690 ----
  local   db1,db2,@demodbs  all                                   md5
  </programlisting>
     </example>
+ 
+    <para>
+     The contents of this file are reflected in the pg_hba_conf view.
+     See <xref linkend="view-pg-hba-conf"> for details.
+    </para>
   </sect1>
  
   <sect1 id="auth-username-maps">
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 411,416 **** CREATE RULE pg_settings_n AS
--- 411,419 ----
  
  GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;
  
+ CREATE VIEW pg_hba_conf AS
+     SELECT * FROM pg_hba_conf() AS A;
+ 
  CREATE VIEW pg_timezone_abbrevs AS
      SELECT * FROM pg_timezone_abbrevs();
  
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 25,38 ****
--- 25,43 ----
  #include <arpa/inet.h>
  #include <unistd.h>
  
+ #include "access/htup_details.h"
  #include "catalog/pg_collation.h"
+ #include "catalog/pg_type.h"
+ #include "funcapi.h"
  #include "libpq/ip.h"
  #include "libpq/libpq.h"
+ #include "miscadmin.h"
  #include "postmaster/postmaster.h"
  #include "regex/regex.h"
  #include "replication/walsender.h"
  #include "storage/fd.h"
  #include "utils/acl.h"
+ #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
***************
*** 74,79 **** typedef struct HbaToken
--- 79,87 ----
  	bool		quoted;
  } HbaToken;
  
+ /* Flag to indicate the failure of reloading pg_hba.conf file */
+ bool		load_hba_failure = false;
+ 
  /*
   * pre-parsed content of HBA config file: list of HbaLine structs.
   * parsed_hba_context is the memory context where it lives.
***************
*** 2220,2222 **** hba_getauthmethod(hbaPort *port)
--- 2228,2733 ----
  {
  	check_hba(port);
  }
+ 
+ 
+ /* LDAP supports 10 currently, keep this well above the most any method needs */
+ #define MAX_OPTIONS 12
+ 
+ /*
+  * Fill in suitable values to build a tuple representing the
+  * HbaLine provided
+  */
+ static void
+ hba_getvalues_for_line(HbaLine *hba, Datum *values, bool *nulls)
+ {
+ 	ListCell   *dbcell;
+ 	char		buffer[NI_MAXHOST];
+ 	StringInfoData str;
+ 	int			index = 0;
+ 	int			noptions;
+ 	Datum		options[MAX_OPTIONS];
+ 
+ 	/* line_number */
+ 	values[index] = Int32GetDatum(hba->linenumber);
+ 
+ 	/* connection type */
+ 	index++;
+ 	switch (hba->conntype)
+ 	{
+ 		case ctLocal:
+ 			values[index] = CStringGetTextDatum("local");
+ 			break;
+ 		case ctHost:
+ 			values[index] = CStringGetTextDatum("host");
+ 			break;
+ 		case ctHostSSL:
+ 			values[index] = CStringGetTextDatum("hostssl");
+ 			break;
+ 		case ctHostNoSSL:
+ 			values[index] = CStringGetTextDatum("hostnossl");
+ 			break;
+ 		default:
+ 			elog(ERROR, "unexpected connection type in parsed HBA entry");
+ 			break;
+ 	}
+ 
+ 	/* databases */
+ 	if (list_length(hba->databases) != 0)
+ 	{
+ 		int			count = 0;
+ 		int			keyword_count = 0;
+ 		HbaToken   *tok;
+ 		Datum	   *databases = palloc(sizeof(Datum) * list_length(hba->databases));
+ 		Datum	   *keyword_databases = palloc(sizeof(Datum) * list_length(hba->databases));
+ 
+ 		foreach(dbcell, hba->databases)
+ 		{
+ 			tok = lfirst(dbcell);
+ 			if (token_is_keyword(tok, "replication")
+ 				|| token_is_keyword(tok, "all")
+ 				|| token_is_keyword(tok, "sameuser")
+ 				|| token_is_keyword(tok, "samegroup")
+ 				|| token_is_keyword(tok, "samerole"))
+ 			{
+ 				keyword_databases[keyword_count++] = CStringGetTextDatum(tok->string);
+ 			}
+ 			else if (tok->quoted)
+ 			{
+ 				char	   *buffer;
+ 				int			buf_len;
+ 
+ 				/* string size + size for quotes + null terminater */
+ 				buf_len = strlen(tok->string) + 2 + 1;
+ 				buffer = palloc(buf_len);
+ 
+ 				snprintf(buffer, buf_len, "\"%s\"", tok->string);
+ 				databases[count++] = CStringGetTextDatum(buffer);
+ 				pfree(buffer);
+ 			}
+ 			else
+ 				databases[count++] = CStringGetTextDatum(tok->string);
+ 		}
+ 
+ 		index++;
+ 		if (keyword_count != 0)
+ 			values[index] = PointerGetDatum(construct_array(keyword_databases, keyword_count,
+ 												   TEXTOID, -1, false, 'i'));
+ 		else
+ 			nulls[index] = true;
+ 
+ 		index++;
+ 		if (count != 0)
+ 			values[index] = PointerGetDatum(construct_array(databases, count,
+ 												   TEXTOID, -1, false, 'i'));
+ 		else
+ 			nulls[index] = true;
+ 	}
+ 	else
+ 	{
+ 		/* No keyword databases */
+ 		index++;
+ 		nulls[index] = true;
+ 
+ 		/* No databases */
+ 		index++;
+ 		nulls[index] = true;
+ 	}
+ 
+ 	/* users */
+ 	if (list_length(hba->roles) != 0)
+ 	{
+ 		int			count = 0;
+ 		int			keyword_count = 0;
+ 		HbaToken   *tok;
+ 		Datum	   *roles = palloc(sizeof(Datum) * list_length(hba->roles));
+ 		Datum	   *keyword_roles = palloc(sizeof(Datum) * list_length(hba->roles));
+ 
+ 		foreach(dbcell, hba->roles)
+ 		{
+ 			tok = lfirst(dbcell);
+ 			if (token_is_keyword(tok, "all")
+ 				|| (!tok->quoted && tok->string[0] == '+'))
+ 				keyword_roles[keyword_count++] = CStringGetTextDatum(tok->string);
+ 			else if (tok->quoted)
+ 			{
+ 				char	   *buffer;
+ 				int			buf_len;
+ 
+ 				/* string size + size for quotes + null terminater */
+ 				buf_len = strlen(tok->string) + 2 + 1;
+ 				buffer = palloc(buf_len);
+ 
+ 				snprintf(buffer, buf_len, "\"%s\"", tok->string);
+ 				roles[count++] = CStringGetTextDatum(buffer);
+ 				pfree(buffer);
+ 			}
+ 			else
+ 				roles[count++] = CStringGetTextDatum(tok->string);
+ 		}
+ 
+ 		index++;
+ 		if (keyword_count != 0)
+ 			values[index] = PointerGetDatum(construct_array(keyword_roles, keyword_count,
+ 												   TEXTOID, -1, false, 'i'));
+ 		else
+ 			nulls[index] = true;
+ 
+ 		index++;
+ 		if (count != 0)
+ 			values[index] = PointerGetDatum(construct_array(roles, count,
+ 												   TEXTOID, -1, false, 'i'));
+ 		else
+ 			nulls[index] = true;
+ 	}
+ 	else
+ 	{
+ 		/* no keyword roles */
+ 		index++;
+ 		nulls[index] = true;
+ 
+ 		/* no roles */
+ 		index++;
+ 		nulls[index] = true;
+ 	}
+ 
+ 	/* address */
+ 	index++;
+ 	if (pg_getnameinfo_all(&hba->addr, sizeof(struct sockaddr_storage),
+ 						   buffer, sizeof(buffer),
+ 						   NULL, 0,
+ 						   NI_NUMERICHOST) == 0)
+ 	{
+ 		clean_ipv6_addr(hba->addr.ss_family, buffer);
+ 		values[index] = DirectFunctionCall1(inet_in, CStringGetDatum(buffer));
+ 	}
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* compare method */
+ 	index++;
+ 	if (hba->conntype == ctLocal)
+ 		nulls[index] = true;
+ 	else if (hba->ip_cmp_method == ipCmpMask)
+ 		values[index] = CStringGetTextDatum("mask");
+ 	else if (hba->ip_cmp_method == ipCmpSameHost)
+ 		values[index] = CStringGetTextDatum("samehost");
+ 	else if (hba->ip_cmp_method == ipCmpSameNet)
+ 		values[index] = CStringGetTextDatum("samenet");
+ 	else if (hba->ip_cmp_method == ipCmpAll)
+ 		values[index] = CStringGetTextDatum("all");
+ 	else
+ 		elog(ERROR, "unexpected compare method in parsed HBA entry");
+ 
+ 	/* hostname */
+ 	index++;
+ 	if (hba->hostname)
+ 		values[index] = CStringGetTextDatum(hba->hostname);
+ 	else
+ 		nulls[index] = true;
+ 
+ 	/* method */
+ 	index++;
+ 	switch (hba->auth_method)
+ 	{
+ 		case uaReject:
+ 			values[index] = CStringGetTextDatum("reject");
+ 			break;
+ 		case uaTrust:
+ 			values[index] = CStringGetTextDatum("trust");
+ 			break;
+ 		case uaIdent:
+ 			values[index] = CStringGetTextDatum("ident");
+ 			break;
+ 		case uaPassword:
+ 			values[index] = CStringGetTextDatum("password");
+ 			break;
+ 		case uaMD5:
+ 			values[index] = CStringGetTextDatum("md5");
+ 			break;
+ 		case uaGSS:
+ 			values[index] = CStringGetTextDatum("gss");
+ 			break;
+ 		case uaSSPI:
+ 			values[index] = CStringGetTextDatum("sspi");
+ 			break;
+ 		case uaPAM:
+ 			values[index] = CStringGetTextDatum("pam");
+ 			break;
+ 		case uaLDAP:
+ 			values[index] = CStringGetTextDatum("ldap");
+ 			break;
+ 		case uaCert:
+ 			values[index] = CStringGetTextDatum("cert");
+ 			break;
+ 		case uaRADIUS:
+ 			values[index] = CStringGetTextDatum("radius");
+ 			break;
+ 		case uaPeer:
+ 			values[index] = CStringGetTextDatum("peer");
+ 			break;
+ 		default:
+ 			elog(ERROR, "unexpected authentication method in parsed HBA entry");
+ 			break;
+ 	}
+ 
+ 	/* options */
+ 	index++;
+ 	noptions = 0;
+ 
+ 	if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
+ 	{
+ 		if (hba->include_realm)
+ 			options[noptions++] = CStringGetTextDatum("include_realm=true");
+ 
+ 		if (hba->krb_realm)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "krb_realm=");
+ 			appendStringInfoString(&str, hba->krb_realm);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	if (hba->usermap)
+ 	{
+ 		initStringInfo(&str);
+ 		appendStringInfoString(&str, "map=");
+ 		appendStringInfoString(&str, hba->usermap);
+ 		options[noptions++] = CStringGetTextDatum(str.data);
+ 	}
+ 
+ 	if (hba->auth_method == uaLDAP)
+ 	{
+ 		if (hba->ldapserver)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapserver=");
+ 			appendStringInfoString(&str, hba->ldapserver);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapport)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->ldapport);
+ 			appendStringInfoString(&str, "ldapport=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldaptls)
+ 			options[noptions++] = CStringGetTextDatum("ldaptls=true");
+ 
+ 		if (hba->ldapprefix)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapprefix=");
+ 			appendStringInfoString(&str, hba->ldapprefix);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapsuffix)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapsuffix=");
+ 			appendStringInfoString(&str, hba->ldapsuffix);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbasedn)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbasedn=");
+ 			appendStringInfoString(&str, hba->ldapbasedn);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbinddn)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbinddn=");
+ 			appendStringInfoString(&str, hba->ldapbinddn);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapbindpasswd)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapbindpasswd=");
+ 			appendStringInfoString(&str, hba->ldapbindpasswd);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapsearchattribute)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "ldapsearchattribute=");
+ 			appendStringInfoString(&str, hba->ldapsearchattribute);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->ldapscope)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->ldapscope);
+ 			appendStringInfoString(&str, "ldapscope=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	if (hba->auth_method == uaRADIUS)
+ 	{
+ 		if (hba->radiusserver)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiusserver=");
+ 			appendStringInfoString(&str, hba->radiusserver);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiussecret)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiussecret=");
+ 			appendStringInfoString(&str, hba->radiussecret);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiusidentifier)
+ 		{
+ 			initStringInfo(&str);
+ 			appendStringInfoString(&str, "radiusidentifier=");
+ 			appendStringInfoString(&str, hba->radiusidentifier);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 
+ 		if (hba->radiusport)
+ 		{
+ 			initStringInfo(&str);
+ 			snprintf(buffer, sizeof(buffer), "%d", hba->radiusport);
+ 			appendStringInfoString(&str, "radiusport=");
+ 			appendStringInfoString(&str, buffer);
+ 			options[noptions++] = CStringGetTextDatum(str.data);
+ 		}
+ 	}
+ 
+ 	Assert(noptions <= MAX_OPTIONS);
+ 	if (noptions)
+ 		values[index] = PointerGetDatum(
+ 				construct_array(options, noptions, TEXTOID, -1, false, 'i'));
+ 	else
+ 		/* Probably should be {} but that makes for a messy looking view */
+ 		nulls[index] = true;
+ }
+ 
+ 
+ #define NUM_PG_HBA_CONF_ATTS   11
+ 
+ /*
+  * SQL-accessible SRF to return all the settings from the pg_hba.conf
+  * file. See the pg_hba_conf view in the "System Catalogs" section of the
+  * manual.
+  */
+ Datum
+ hba_conf(PG_FUNCTION_ARGS)
+ {
+ 	Tuplestorestate *tuple_store;
+ 	TupleDesc	tupdesc;
+ 	ListCell   *line;
+ 	MemoryContext old_cxt;
+ 
+ 	/*
+ 	 * We must use the Materialize mode to be safe against HBA file reloads
+ 	 * while the cursor is open. It's also more efficient than having to look
+ 	 * up our current position in the parsed list every time.
+ 	 */
+ 
+ 	ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+ 
+ 	if (!superuser())
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 				 (errmsg("only superuser can view pg_hba.conf settings"))));
+ 
+ 	if (load_hba_failure)
+ 		ereport(WARNING,
+ 			 (errmsg("There was some failure in reloading pg_hba.conf file. "
+ 		   "The pg_hba.conf settings data may contains stale information")));
+ 
+ 	if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ 		(rsi->allowedModes & SFRM_Materialize) == 0)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("set-valued function called in context that "
+ 						"cannot accept a set")));
+ 
+ 	rsi->returnMode = SFRM_Materialize;
+ 
+ 	/*
+ 	 * Create the tupledesc and tuplestore in the per_query context as
+ 	 * required for SFRM_Materialize.
+ 	 */
+ 	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ 
+ 	tupdesc = CreateTemplateTupleDesc(NUM_PG_HBA_CONF_ATTS, false);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "line_number",
+ 					   INT4OID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "type",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "keyword_databases",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "databases",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "keyword_users",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "users",
+ 					   TEXTARRAYOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "address",
+ 					   INETOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "compare_method",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "hostname",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "method",
+ 					   TEXTOID, -1, 0);
+ 	TupleDescInitEntry(tupdesc, (AttrNumber) 11, "options",
+ 					   TEXTARRAYOID, -1, 0);
+ 	BlessTupleDesc(tupdesc);
+ 
+ 	tuple_store =
+ 		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ 							  false, work_mem);
+ 
+ 	MemoryContextSwitchTo(old_cxt);
+ 
+ 	/*
+ 	 * Loop through the list and deparse each entry as it comes, storing it in
+ 	 * the tuplestore. Any temporary memory allocations here live only for the
+ 	 * function call lifetime.
+ 	 */
+ 	foreach(line, parsed_hba_lines)
+ 	{
+ 		HbaLine    *hba = (HbaLine *) lfirst(line);
+ 		Datum		values[NUM_PG_HBA_CONF_ATTS];
+ 		bool		nulls[NUM_PG_HBA_CONF_ATTS];
+ 		HeapTuple	tuple;
+ 
+ 		MemSet(values, 0, sizeof(values));
+ 		MemSet(nulls, 0, sizeof(nulls));
+ 
+ 		CHECK_FOR_INTERRUPTS();
+ 
+ 		/* Get the next parsed hba line values */
+ 		hba_getvalues_for_line(hba, values, nulls);
+ 
+ 		/* build a tuple */
+ 		tuple = heap_form_tuple(tupdesc, values, nulls);
+ 		tuplestore_puttuple(tuple_store, tuple);
+ 	}
+ 
+ 	rsi->setDesc = tupdesc;
+ 	rsi->setResult = tuple_store;
+ 
+ 	PG_RETURN_NULL();
+ }
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
***************
*** 3996,4001 **** PostgresMain(int argc, char *argv[],
--- 3996,4014 ----
  		{
  			got_SIGHUP = false;
  			ProcessConfigFile(PGC_SIGHUP);
+ 			
+ 			/* 
+ 			 * Reload authentication config files too to refresh 
+ 			 * pg_hba_conf view data.
+ 			 */
+ 			if (!load_hba())
+ 			{
+ 				ereport(DEBUG1,
+ 					(errmsg("Falure in reloading pg_hba.conf, pg_hba_conf view may show stale information")));
+ 				load_hba_failure = true;
+ 			}
+ 			
+ 			load_hba_failure = false;
  		}
  
  		/*
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 3048,3053 **** DATA(insert OID = 2078 (  set_config		PGNSP PGUID 12 1 0 0 0 f f f f f f v 3 0 2
--- 3048,3055 ----
  DESCR("SET X as a function");
  DATA(insert OID = 2084 (  pg_show_all_settings	PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{25,25,25,25,25,25,25,25,25,25,25,1009,25,25,25,23}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{name,setting,unit,category,short_desc,extra_desc,context,vartype,source,min_val,max_val,enumvals,boot_val,reset_val,sourcefile,sourceline}" _null_ show_all_settings _null_ _null_ _null_ ));
  DESCR("SHOW ALL as a function");
+ DATA(insert OID = 3582 (  pg_hba_conf  PGNSP PGUID 12 1 1000 0 0 f f f f t t s 0 0 2249 "" "{23,25,1009,1009,1009,1009,869,25,25,25,1009}" "{o,o,o,o,o,o,o,o,o,o,o}" "{line_number,type,keyword_databases,databases,keyword_users,users,address,compare_method,hostname,method,options}" _null_ hba_conf _null_ _null_ _null_ ));
+ DESCR("view client authentication settings");
  DATA(insert OID = 1371 (  pg_lock_status   PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}" _null_ pg_lock_status _null_ _null_ _null_ ));
  DESCR("view system lock information");
  DATA(insert OID = 1065 (  pg_prepared_xact PGNSP PGUID 12 1 1000 0 0 f f f f t t v 0 0 2249 "" "{28,25,1184,26,26}" "{o,o,o,o,o}" "{transaction,gid,prepared,ownerid,dbid}" _null_ pg_prepared_xact _null_ _null_ _null_ ));
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 96,101 **** typedef struct IdentLine
--- 96,103 ----
  /* kluge to avoid including libpq/libpq-be.h here */
  typedef struct Port hbaPort;
  
+ extern bool load_hba_failure;
+ 
  extern bool load_hba(void);
  extern bool load_ident(void);
  extern void hba_getauthmethod(hbaPort *port);
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 1097,1102 **** extern Datum quote_nullable(PG_FUNCTION_ARGS);
--- 1097,1103 ----
  extern Datum show_config_by_name(PG_FUNCTION_ARGS);
  extern Datum set_config_by_name(PG_FUNCTION_ARGS);
  extern Datum show_all_settings(PG_FUNCTION_ARGS);
+ extern Datum hba_conf(PG_FUNCTION_ARGS);
  
  /* lockfuncs.c */
  extern Datum pg_lock_status(PG_FUNCTION_ARGS);
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
***************
*** 1315,1320 **** pg_group| SELECT pg_authid.rolname AS groname,
--- 1315,1332 ----
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
    WHERE (NOT pg_authid.rolcanlogin);
+ pg_hba_conf| SELECT a.line_number,
+     a.type,
+     a.keyword_databases,
+     a.databases,
+     a.keyword_users,
+     a.users,
+     a.address,
+     a.compare_method,
+     a.hostname,
+     a.method,
+     a.options
+    FROM pg_hba_conf() a(line_number, type, keyword_databases, databases, keyword_users, users, address, compare_method, hostname, method, options);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,


^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread

* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 293+ messages in thread

From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)

---
 src/backend/commands/statscmds.c   | 26 +++++-----------
 src/backend/commands/tablecmds.c   |  4 +--
 src/backend/parser/parse_utilcmd.c | 13 +++++---
 src/backend/tcop/utility.c         | 48 +++++++++++++++++-------------
 src/include/commands/defrem.h      |  2 +-
 src/include/parser/parse_utilcmd.h |  2 +-
 6 files changed, 49 insertions(+), 46 deletions(-)

diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
  *		CREATE STATISTICS
  */
 ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
 {
 	int16		attnums[STATS_MAX_DIMENSIONS];
 	int			nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
 	ListCell   *cell;
 	ListCell   *cell2;
 
-	Assert(IsA(stmt, CreateStatsStmt));
-
 	/*
 	 * Examine the FROM clause.  Currently, we only allow it to be a single
 	 * simple table, but later we'll probably allow multiple tables and JOIN
-	 * syntax.  The grammar is already prepared for that, so we have to check
-	 * here that what we got is what we can support.
+	 * syntax.  Parse analysis checked the list length already, so this is
+	 * just defense-in-depth.
 	 */
-	if (list_length(stmt->relations) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+	Assert(list_length(stmt->relations) == list_length(relids));
+	if (list_length(relids) != 1)
+		elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
 
-	foreach(cell, stmt->relations)
+	foreach_oid(relid, relids)
 	{
-		Node	   *rln = (Node *) lfirst(cell);
-
-		if (!IsA(rln, RangeVar))
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
 		/*
 		 * CREATE STATISTICS will influence future execution plans but does
 		 * not interfere with currently executing plans.  So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 		 * conflicting with ANALYZE and other DDL that sets statistical
 		 * information, but not with normal queries.
 		 */
-		rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+		rel = relation_open(relid, ShareUpdateExclusiveLock);
 
 		/* Restrict to allowed relation types */
 		if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
 	/* The CreateStatsStmt has already been through transformStatsStmt */
 	Assert(stmt->transformed);
 
-	address = CreateStatistics(stmt);
+	address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
 
 	return address;
 }
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		}
 		else if (IsA(stmt, CreateStatsStmt))
 			querytree_list = lappend(querytree_list,
-									 transformStatsStmt(oldRelId,
+									 transformStatsStmt(list_make1_oid(oldRelId),
 														(CreateStatsStmt *) stmt,
 														cmd));
 		else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
  * transformStatsStmt - parse analysis for CREATE STATISTICS
  *
  * To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
  */
 CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
 	ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	if (stmt->transformed)
 		return stmt;
 
+	if (list_length(relids) != 1)
+		ereport(ERROR,
+				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
 	 * to its fields without qualification.  Caller is responsible for locking
 	 * relation, but we still need to open it.
 	 */
-	rel = relation_open(relid, NoLock);
+	rel = relation_open(linitial_oid(relids), NoLock);
 	nsitem = addRangeTableEntryForRelation(pstate, rel,
 										   AccessShareLock,
 										   NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
 
 			case T_CreateStatsStmt:
 				{
-					Oid			relid;
 					CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
-					RangeVar   *rel = (RangeVar *) linitial(stmt->relations);
+					List	   *relids = NIL;
+					ListCell   *cell;
 
-					if (!IsA(rel, RangeVar))
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("only a single relation is allowed in CREATE STATISTICS")));
+					foreach(cell, stmt->relations)
+					{
+						Oid			relid;
 
-					/*
-					 * CREATE STATISTICS will influence future execution plans
-					 * but does not interfere with currently executing plans.
-					 * So it should be enough to take ShareUpdateExclusiveLock
-					 * on relation, conflicting with ANALYZE and other DDL
-					 * that sets statistical information, but not with normal
-					 * queries.
-					 *
-					 * XXX RangeVarCallbackOwnsRelation not needed here, to
-					 * keep the same behavior as before.
-					 */
-					relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+						if (!IsA(lfirst(cell), RangeVar))
+							ereport(ERROR,
+									errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+									errmsg("cannot create statistics on specified relation"),
+									errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
 
+						/*
+						 * CREATE STATISTICS will influence future execution
+						 * plans but does not interfere with currently
+						 * executing plans.  So it should be enough to take
+						 * ShareUpdateExclusiveLock on relation, conflicting
+						 * with ANALYZE and other DDL that sets statistical
+						 * information, but not with normal queries.
+						 *
+						 * XXX RangeVarCallbackOwnsRelation not needed here,
+						 * to keep the same behavior as before.
+						 */
+						relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+												 ShareUpdateExclusiveLock, false);
+						relids = lappend_oid(relids, relid);
+					}
 					/* Run parse analysis ... */
-					stmt = transformStatsStmt(relid, stmt, queryString);
+					stmt = transformStatsStmt(relids, stmt, queryString);
 
-					address = CreateStatistics(stmt);
+					/* ... and execute the command */
+					address = CreateStatistics(stmt, relids);
 				}
 				break;
 
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
 extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
 extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
 									 const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
 										   const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 							  List **actions, Node **whereClause);
-- 
2.39.5


--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0001-CREATE-STATISTICS-Fix-error-message.patch"



^ permalink  raw  reply  [nested|flat] 293+ messages in thread


end of thread, other threads:[~2025-08-28 17:49 UTC | newest]

Thread overview: 293+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2015-03-03 15:08 Re: Providing catalog view to pg_hba.conf file - Patch submission Greg Stark <[email protected]>
2015-03-03 18:05 ` Jim Nasby <[email protected]>
2015-03-03 18:57   ` Greg Stark <[email protected]>
2015-03-04 00:17     ` Jim Nasby <[email protected]>
2015-03-04 01:07       ` Greg Stark <[email protected]>
2015-03-04 01:36         ` Josh Berkus <[email protected]>
2015-03-04 21:41       ` Peter Eisentraut <[email protected]>
2015-03-05 12:46         ` Pavel Stehule <[email protected]>
2015-03-05 14:42         ` Greg Stark <[email protected]>
2015-03-06 03:11           ` Peter Eisentraut <[email protected]>
2015-03-11 17:32             ` Greg Stark <[email protected]>
2015-03-16 15:57               ` Robert Haas <[email protected]>
2015-03-16 16:29                 ` Alvaro Herrera <[email protected]>
2015-03-16 16:49                   ` Greg Stark <[email protected]>
2015-03-16 17:46                   ` David G. Johnston <[email protected]>
2015-03-16 18:08                     ` Robert Haas <[email protected]>
2015-03-16 18:11                     ` Greg Stark <[email protected]>
2015-03-16 19:23                       ` David G. Johnston <[email protected]>
2015-03-04 00:34     ` Haribabu Kommi <[email protected]>
2015-03-04 01:18       ` Greg Stark <[email protected]>
2015-03-04 01:35         ` Haribabu Kommi <[email protected]>
2015-03-04 06:34           ` Haribabu Kommi <[email protected]>
2015-03-13 02:33             ` Peter Eisentraut <[email protected]>
2015-03-27 08:23               ` Haribabu Kommi <[email protected]>
2015-03-29 17:34                 ` Pavel Stehule <[email protected]>
2015-03-31 12:38                   ` Haribabu Kommi <[email protected]>
2015-03-04 12:32           ` Greg Stark <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox